Format source code using google-java-format 1.24.0 Version 1.7 which was used in Gerrit core doesn't support Java 21 features used in this plugin. Change updating google-java-format in Gerrit core is I8607721e9e83519fa2bab415462ace5255032068. Change-Id: I3dde13c0274027cb05c963f1172e93f4033bba51
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java index 9a4f4f9..e2f35cd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java
@@ -6,9 +6,10 @@ import com.googlesource.gerrit.plugins.aicodereview.listener.GerritListener; public class Module extends AbstractModule { - @Override - protected void configure() { - Multibinder<EventListener> eventListenerBinder = Multibinder.newSetBinder(binder(), EventListener.class); - eventListenerBinder.addBinding().to(GerritListener.class); - } + @Override + protected void configure() { + Multibinder<EventListener> eventListenerBinder = + Multibinder.newSetBinder(binder(), EventListener.class); + eventListenerBinder.addBinding().to(GerritListener.class); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java index 7bcea8f..beb91ae 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java
@@ -11,166 +11,172 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientReview; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages.DebugCodeBlocksReview; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.comment.GerritCommentRange; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritCodeRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.review.ReviewBatch; import com.googlesource.gerrit.plugins.aicodereview.settings.Settings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.*; - @Slf4j public class PatchSetReviewer { - private static final String SPLIT_REVIEW_MSG = "Too many changes. Please consider splitting into patches smaller " + - "than %s lines for review."; + private static final String SPLIT_REVIEW_MSG = + "Too many changes. Please consider splitting into patches smaller " + + "than %s lines for review."; - private final Configuration config; - private final GerritClient gerritClient; - private final ChangeSetData changeSetData; - private final Provider<GerritClientReview> clientReviewProvider; - @Getter - private final ChatAIClient chatGptClient; - private final Localizer localizer; - private final DebugCodeBlocksReview debugCodeBlocksReview; + private final Configuration config; + private final GerritClient gerritClient; + private final ChangeSetData changeSetData; + private final Provider<GerritClientReview> clientReviewProvider; + @Getter private final ChatAIClient chatGptClient; + private final Localizer localizer; + private final DebugCodeBlocksReview debugCodeBlocksReview; - private GerritCommentRange gerritCommentRange; - private List<ReviewBatch> reviewBatches; - private List<GerritComment> commentProperties; - private List<Integer> reviewScores; + private GerritCommentRange gerritCommentRange; + private List<ReviewBatch> reviewBatches; + private List<GerritComment> commentProperties; + private List<Integer> reviewScores; - @Inject - PatchSetReviewer( - GerritClient gerritClient, - Configuration config, - ChangeSetData changeSetData, - Provider<GerritClientReview> clientReviewProvider, - ChatAIClient chatGptClient, - Localizer localizer - ) { - this.config = config; - this.gerritClient = gerritClient; - this.changeSetData = changeSetData; - this.clientReviewProvider = clientReviewProvider; - this.chatGptClient = chatGptClient; - this.localizer = localizer; - debugCodeBlocksReview = new DebugCodeBlocksReview(localizer); + @Inject + PatchSetReviewer( + GerritClient gerritClient, + Configuration config, + ChangeSetData changeSetData, + Provider<GerritClientReview> clientReviewProvider, + ChatAIClient chatGptClient, + Localizer localizer) { + this.config = config; + this.gerritClient = gerritClient; + this.changeSetData = changeSetData; + this.clientReviewProvider = clientReviewProvider; + this.chatGptClient = chatGptClient; + this.localizer = localizer; + debugCodeBlocksReview = new DebugCodeBlocksReview(localizer); + } + + public void review(GerritChange change) throws Exception { + reviewBatches = new ArrayList<>(); + reviewScores = new ArrayList<>(); + commentProperties = gerritClient.getClientData(change).getCommentProperties(); + gerritCommentRange = new GerritCommentRange(gerritClient, change); + String patchSet = gerritClient.getPatchSet(change); + if (patchSet.isEmpty() && config.getAIMode() == Settings.Modes.stateless) { + log.info("No file to review has been found in the PatchSet"); + return; + } + ChangeSetDataHandler.update(config, change, gerritClient, changeSetData, localizer); + + if (changeSetData.shouldRequestAICodeReview()) { + AIChatResponseContent reviewReply = getReviewReply(change, patchSet); + log.debug("ChatGPT response: {}", reviewReply); + + retrieveReviewBatches(reviewReply, change); + } + clientReviewProvider + .get() + .setReview(change, reviewBatches, changeSetData, getReviewScore(change)); + } + + private void setCommentBatchMap(ReviewBatch batchMap, Integer batchID) { + if (commentProperties != null && batchID < commentProperties.size()) { + GerritComment commentProperty = commentProperties.get(batchID); + if (commentProperty != null + && (commentProperty.getLine() != null || commentProperty.getRange() != null)) { + String id = commentProperty.getId(); + String filename = commentProperty.getFilename(); + Integer line = commentProperty.getLine(); + GerritCodeRange range = commentProperty.getRange(); + if (range != null) { + batchMap.setId(id); + batchMap.setFilename(filename); + batchMap.setLine(line); + batchMap.setRange(range); + } + } + } + } + + private void setPatchSetReviewBatchMap(ReviewBatch batchMap, AIChatReplyItem replyItem) { + Optional<GerritCodeRange> optGerritCommentRange = + gerritCommentRange.getGerritCommentRange(replyItem); + if (optGerritCommentRange.isPresent()) { + GerritCodeRange gerritCodeRange = optGerritCommentRange.get(); + batchMap.setFilename(replyItem.getFilename()); + batchMap.setLine(gerritCodeRange.getStartLine()); + batchMap.setRange(gerritCodeRange); + } + } + + private void retrieveReviewBatches(AIChatResponseContent reviewReply, GerritChange change) { + if (reviewReply.getMessageContent() != null && !reviewReply.getMessageContent().isEmpty()) { + reviewBatches.add(new ReviewBatch(reviewReply.getMessageContent())); + return; + } + for (AIChatReplyItem replyItem : reviewReply.getReplies()) { + String reply = replyItem.getReply(); + Integer score = replyItem.getScore(); + boolean isNotNegative = isNotNegativeReply(score); + boolean isIrrelevant = isIrrelevantReply(replyItem); + boolean isHidden = + replyItem.isRepeated() || replyItem.isConflicting() || isIrrelevant || isNotNegative; + if (!replyItem.isConflicting() && !isIrrelevant && score != null) { + log.debug("Score added: {}", score); + reviewScores.add(score); + } + if (changeSetData.getReplyFilterEnabled() && isHidden) { + continue; + } + if (changeSetData.getDebugReviewMode()) { + reply += debugCodeBlocksReview.getDebugCodeBlock(replyItem, isHidden); + } + ReviewBatch batchMap = new ReviewBatch(reply); + if (change.getIsCommentEvent() && replyItem.getId() != null) { + setCommentBatchMap(batchMap, replyItem.getId()); + } else { + setPatchSetReviewBatchMap(batchMap, replyItem); + } + reviewBatches.add(batchMap); + } + } + + private AIChatResponseContent getReviewReply(GerritChange change, String patchSet) + throws Exception { + List<String> patchLines = Arrays.asList(patchSet.split("\n")); + if (patchLines.size() > config.getMaxReviewLines()) { + log.warn("Patch set too large. Skipping review. changeId: {}", change.getFullChangeId()); + return new AIChatResponseContent(String.format(SPLIT_REVIEW_MSG, config.getMaxReviewLines())); } - public void review(GerritChange change) throws Exception { - reviewBatches = new ArrayList<>(); - reviewScores = new ArrayList<>(); - commentProperties = gerritClient.getClientData(change).getCommentProperties(); - gerritCommentRange = new GerritCommentRange(gerritClient, change); - String patchSet = gerritClient.getPatchSet(change); - if (patchSet.isEmpty() && config.getAIMode() == Settings.Modes.stateless) { - log.info("No file to review has been found in the PatchSet"); - return; - } - ChangeSetDataHandler.update(config, change, gerritClient, changeSetData, localizer); + return chatGptClient.ask(changeSetData, change, patchSet); + } - if (changeSetData.shouldRequestAICodeReview()) { - AIChatResponseContent reviewReply = getReviewReply(change, patchSet); - log.debug("ChatGPT response: {}", reviewReply); - - retrieveReviewBatches(reviewReply, change); - } - clientReviewProvider.get().setReview(change, reviewBatches, changeSetData, getReviewScore(change)); + private Integer getReviewScore(GerritChange change) { + if (config.isVotingEnabled()) { + return reviewScores.isEmpty() + ? (change.getIsCommentEvent() ? null : 0) + : Collections.min(reviewScores); + } else { + return null; } + } - private void setCommentBatchMap(ReviewBatch batchMap, Integer batchID) { - if (commentProperties != null && batchID < commentProperties.size()) { - GerritComment commentProperty = commentProperties.get(batchID); - if (commentProperty != null && (commentProperty.getLine() != null || commentProperty.getRange() != null)) { - String id = commentProperty.getId(); - String filename = commentProperty.getFilename(); - Integer line = commentProperty.getLine(); - GerritCodeRange range = commentProperty.getRange(); - if (range != null) { - batchMap.setId(id); - batchMap.setFilename(filename); - batchMap.setLine(line); - batchMap.setRange(range); - } - } - } - } + private boolean isNotNegativeReply(Integer score) { + return score != null + && config.getFilterNegativeComments() + && score >= config.getFilterCommentsBelowScore(); + } - private void setPatchSetReviewBatchMap(ReviewBatch batchMap, AIChatReplyItem replyItem) { - Optional<GerritCodeRange> optGerritCommentRange = gerritCommentRange.getGerritCommentRange(replyItem); - if (optGerritCommentRange.isPresent()) { - GerritCodeRange gerritCodeRange = optGerritCommentRange.get(); - batchMap.setFilename(replyItem.getFilename()); - batchMap.setLine(gerritCodeRange.getStartLine()); - batchMap.setRange(gerritCodeRange); - } - } - - private void retrieveReviewBatches(AIChatResponseContent reviewReply, GerritChange change) { - if (reviewReply.getMessageContent() != null && !reviewReply.getMessageContent().isEmpty()) { - reviewBatches.add(new ReviewBatch(reviewReply.getMessageContent())); - return; - } - for (AIChatReplyItem replyItem : reviewReply.getReplies()) { - String reply = replyItem.getReply(); - Integer score = replyItem.getScore(); - boolean isNotNegative = isNotNegativeReply(score); - boolean isIrrelevant = isIrrelevantReply(replyItem); - boolean isHidden = replyItem.isRepeated() || replyItem.isConflicting() || isIrrelevant || isNotNegative; - if (!replyItem.isConflicting() && !isIrrelevant && score != null) { - log.debug("Score added: {}", score); - reviewScores.add(score); - } - if (changeSetData.getReplyFilterEnabled() && isHidden) { - continue; - } - if (changeSetData.getDebugReviewMode()) { - reply += debugCodeBlocksReview.getDebugCodeBlock(replyItem, isHidden); - } - ReviewBatch batchMap = new ReviewBatch(reply); - if (change.getIsCommentEvent() && replyItem.getId() != null) { - setCommentBatchMap(batchMap, replyItem.getId()); - } - else { - setPatchSetReviewBatchMap(batchMap, replyItem); - } - reviewBatches.add(batchMap); - } - } - - private AIChatResponseContent getReviewReply(GerritChange change, String patchSet) throws Exception { - List<String> patchLines = Arrays.asList(patchSet.split("\n")); - if (patchLines.size() > config.getMaxReviewLines()) { - log.warn("Patch set too large. Skipping review. changeId: {}", change.getFullChangeId()); - return new AIChatResponseContent(String.format(SPLIT_REVIEW_MSG, config.getMaxReviewLines())); - } - - return chatGptClient.ask(changeSetData, change, patchSet); - } - - private Integer getReviewScore(GerritChange change) { - if (config.isVotingEnabled()) { - return reviewScores.isEmpty() ? - (change.getIsCommentEvent() ? null : 0) : - Collections.min(reviewScores); - } - else { - return null; - } - } - - private boolean isNotNegativeReply(Integer score) { - return score != null && - config.getFilterNegativeComments() && - score >= config.getFilterCommentsBelowScore(); - } - - private boolean isIrrelevantReply(AIChatReplyItem replyItem) { - return config.getFilterRelevantComments() && - replyItem.getRelevance() != null && - replyItem.getRelevance() < config.getFilterCommentsRelevanceThreshold(); - } + private boolean isIrrelevantReply(AIChatReplyItem replyItem) { + return config.getFilterRelevantComments() + && replyItem.getRelevance() != null + && replyItem.getRelevance() < config.getFilterCommentsRelevanceThreshold(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/ConfigCreator.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/ConfigCreator.java index fc22c23..7a0a468 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/ConfigCreator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/ConfigCreator.java
@@ -1,12 +1,14 @@ package com.googlesource.gerrit.plugins.aicodereview.config; +import static com.googlesource.gerrit.plugins.aicodereview.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG; + import com.google.gerrit.entities.Account; import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountState; -import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.project.NoSuchProjectException; @@ -14,111 +16,107 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerBaseProvider; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jgit.lib.Config; - import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; - -import static com.googlesource.gerrit.plugins.aicodereview.config.DynamicConfiguration.KEY_DYNAMIC_CONFIG; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jgit.lib.Config; @Singleton @Slf4j public class ConfigCreator { - private final String pluginName; + private final String pluginName; - private final AccountCache accountCache; - private final PluginConfigFactory configFactory; + private final AccountCache accountCache; + private final PluginConfigFactory configFactory; - private final OneOffRequestContext context; - private final GerritApi gerritApi; - private final PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider; + private final OneOffRequestContext context; + private final GerritApi gerritApi; + private final PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider; - @Inject - ConfigCreator( - @PluginName String pluginName, - AccountCache accountCache, - PluginConfigFactory configFactory, - OneOffRequestContext context, - GerritApi gerritApi, - PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider - ) { - this.pluginName = pluginName; - this.accountCache = accountCache; - this.configFactory = configFactory; - this.context = context; - this.gerritApi = gerritApi; - this.pluginDataHandlerBaseProvider = pluginDataHandlerBaseProvider; + @Inject + ConfigCreator( + @PluginName String pluginName, + AccountCache accountCache, + PluginConfigFactory configFactory, + OneOffRequestContext context, + GerritApi gerritApi, + PluginDataHandlerBaseProvider pluginDataHandlerBaseProvider) { + this.pluginName = pluginName; + this.accountCache = accountCache; + this.configFactory = configFactory; + this.context = context; + this.gerritApi = gerritApi; + this.pluginDataHandlerBaseProvider = pluginDataHandlerBaseProvider; + } + + public Configuration createConfig(Project.NameKey projectName, Change.Key changeKey) + throws NoSuchProjectException { + PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName); + log.debug( + "These configuration items have been set in the global configuration: {}", + globalConfig.getNames()); + PluginConfig projectConfig = configFactory.getFromProjectConfig(projectName, pluginName); + log.debug( + "These configuration items have been set in the project configuration: {}", + projectConfig.getNames()); + // `PluginDataHandlerProvider` cannot be injected because `GerritChange` is not initialized at + // this stage: + // instead of using `PluginDataHandlerProvider.getChangeScope`, + // `PluginDataHandlerBaseProvider.get` is employed + Map<String, String> dynamicConfig = + pluginDataHandlerBaseProvider + .get(changeKey.toString()) + .getJsonValue(KEY_DYNAMIC_CONFIG, String.class); + if (dynamicConfig != null && !dynamicConfig.isEmpty()) { + log.info("DynamicConfig found for change '{}': {}", changeKey, dynamicConfig); + projectConfig = updateDynamicConfig(projectConfig, pluginName, dynamicConfig); } + Optional<AccountState> codeReviewAccount = getAccount(globalConfig); + String email = codeReviewAccount.map(a -> a.account().preferredEmail()).orElse(""); + Account.Id accountId = + codeReviewAccount + .map(a -> a.account().id()) + .orElseThrow( + () -> + new RuntimeException( + String.format( + "Given account %s doesn't exist", + globalConfig.getString(Configuration.KEY_GERRIT_USERNAME)))); + return new Configuration(context, gerritApi, globalConfig, projectConfig, email, accountId); + } - public Configuration createConfig(Project.NameKey projectName, Change.Key changeKey) throws NoSuchProjectException { - PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName); - log.debug( - "These configuration items have been set in the global configuration: {}", - globalConfig.getNames()); - PluginConfig projectConfig = configFactory.getFromProjectConfig(projectName, pluginName); - log.debug( - "These configuration items have been set in the project configuration: {}", - projectConfig.getNames()); - // `PluginDataHandlerProvider` cannot be injected because `GerritChange` is not initialized at this stage: - // instead of using `PluginDataHandlerProvider.getChangeScope`, `PluginDataHandlerBaseProvider.get` is employed - Map<String, String> dynamicConfig = pluginDataHandlerBaseProvider.get(changeKey.toString()) - .getJsonValue(KEY_DYNAMIC_CONFIG, String.class); - if (dynamicConfig != null && !dynamicConfig.isEmpty()) { - log.info("DynamicConfig found for change '{}': {}", changeKey, dynamicConfig); - projectConfig = updateDynamicConfig(projectConfig, pluginName, dynamicConfig); - } - Optional<AccountState> codeReviewAccount = getAccount(globalConfig); - String email = codeReviewAccount.map(a -> a.account().preferredEmail()).orElse(""); - Account.Id accountId = - codeReviewAccount - .map(a -> a.account().id()) - .orElseThrow( - () -> - new RuntimeException( - String.format( - "Given account %s doesn't exist", - globalConfig.getString(Configuration.KEY_GERRIT_USERNAME)))); - return new Configuration(context, gerritApi, globalConfig, projectConfig, email, accountId); + private Optional<AccountState> getAccount(PluginConfig globalConfig) { + String codeReviewUser = globalConfig.getString(Configuration.KEY_GERRIT_USERNAME); + return accountCache.getByUsername(codeReviewUser); + } + + private PluginConfig updateDynamicConfig( + PluginConfig projectConfig, String pluginName, Map<String, String> dynamicConfig) { + // Retrieve all current configuration values + Set<String> keys = projectConfig.getNames(); + Map<String, String> currentConfigValues = new HashMap<>(); + for (String key : keys) { + currentConfigValues.put(key, projectConfig.getString(key)); } + // Merge current config with new values from dynamicConfig + currentConfigValues.putAll(dynamicConfig); + PluginConfig.Update configUpdater = getProjectUpdate(pluginName, currentConfigValues); - private Optional<AccountState> getAccount(PluginConfig globalConfig) { - String codeReviewUser = globalConfig.getString(Configuration.KEY_GERRIT_USERNAME); - return accountCache.getByUsername(codeReviewUser); + return configUpdater.asPluginConfig(); + } + + private PluginConfig.@NonNull Update getProjectUpdate( + String pluginName, Map<String, String> currentConfigValues) { + // Use PluginConfig.Update to apply merged configuration + PluginConfig.Update configUpdater = + new PluginConfig.Update(pluginName, new Config(), Optional.empty()); + // Set all merged values + for (Map.Entry<String, String> entry : currentConfigValues.entrySet()) { + configUpdater.setString(entry.getKey(), entry.getValue()); } - - private PluginConfig updateDynamicConfig( - PluginConfig projectConfig, - String pluginName, - Map<String, String> dynamicConfig - ) { - // Retrieve all current configuration values - Set<String> keys = projectConfig.getNames(); - Map<String, String> currentConfigValues = new HashMap<>(); - for (String key : keys) { - currentConfigValues.put(key, projectConfig.getString(key)); - } - // Merge current config with new values from dynamicConfig - currentConfigValues.putAll(dynamicConfig); - PluginConfig.Update configUpdater = getProjectUpdate(pluginName, currentConfigValues); - - return configUpdater.asPluginConfig(); - } - - private PluginConfig.@NonNull Update getProjectUpdate(String pluginName, Map<String, String> currentConfigValues) { - // Use PluginConfig.Update to apply merged configuration - PluginConfig.Update configUpdater = new PluginConfig.Update( - pluginName, - new Config(), - Optional.empty() - ); - // Set all merged values - for (Map.Entry<String, String> entry : currentConfigValues.entrySet()) { - configUpdater.setString(entry.getKey(), entry.getValue()); - } - return configUpdater; - } + return configUpdater; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/Configuration.java index c79a24b..4caebfa 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/Configuration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/Configuration.java
@@ -7,412 +7,404 @@ import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.util.ManualRequestContext; import com.google.gerrit.server.util.OneOffRequestContext; - +import com.googlesource.gerrit.plugins.aicodereview.settings.Settings.AIType; +import com.googlesource.gerrit.plugins.aicodereview.settings.Settings.Modes; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.jetbrains.annotations.NotNull; -import java.util.*; -import java.util.regex.Pattern; - -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.Modes; -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.AIType; - - @Slf4j public class Configuration { - // Config Constants - public static final String ENABLED_USERS_ALL = "ALL"; - public static final String ENABLED_GROUPS_ALL = "ALL"; - public static final String ENABLED_TOPICS_ALL = "ALL"; - public static final String NOT_CONFIGURED_ERROR_MSG = "%s is not configured"; + // Config Constants + public static final String ENABLED_USERS_ALL = "ALL"; + public static final String ENABLED_GROUPS_ALL = "ALL"; + public static final String ENABLED_TOPICS_ALL = "ALL"; + public static final String NOT_CONFIGURED_ERROR_MSG = "%s is not configured"; - // Default Config values - public static final String OPENAI_DOMAIN = "https://api.openai.com"; - public static final String DEFAULT_CHATGPT_MODEL = "gpt-4o"; - public static final String DEFAULT_OPENAI_MODEL = "qwen2.5-coder"; + // Default Config values + public static final String OPENAI_DOMAIN = "https://api.openai.com"; + public static final String DEFAULT_CHATGPT_MODEL = "gpt-4o"; + public static final String DEFAULT_OPENAI_MODEL = "qwen2.5-coder"; - public static final double DEFAULT_AI_CHAT_REVIEW_TEMPERATURE = 0.2; - public static final double DEFAULT_AI_CHAT_COMMENT_TEMPERATURE = 1.0; + public static final double DEFAULT_AI_CHAT_REVIEW_TEMPERATURE = 0.2; + public static final double DEFAULT_AI_CHAT_COMMENT_TEMPERATURE = 1.0; - private static final String DEFAULT_AI_MODE = "stateless"; - private static final boolean DEFAULT_REVIEW_PATCH_SET = true; - private static final boolean DEFAULT_REVIEW_COMMIT_MESSAGES = true; - private static final boolean DEFAULT_FULL_FILE_REVIEW = true; - private static final boolean DEFAULT_STREAM_OUTPUT = false; - private static final boolean DEFAULT_GLOBAL_ENABLE = false; - private static final String DEFAULT_DISABLED_USERS = ""; - private static final String DEFAULT_ENABLED_USERS = ENABLED_USERS_ALL; - private static final String DEFAULT_DISABLED_GROUPS = ""; - private static final String DEFAULT_ENABLED_GROUPS = ENABLED_GROUPS_ALL; - private static final String DEFAULT_DISABLED_TOPIC_FILTER = ""; - private static final String DEFAULT_ENABLED_TOPIC_FILTER = ENABLED_TOPICS_ALL; - private static final String DEFAULT_ENABLED_PROJECTS = ""; - private static final String DEFAULT_ENABLED_FILE_EXTENSIONS = String.join(",", new String[]{ - ".py", - ".java", - ".js", - ".ts", - ".html", - ".css", - ".cs", - ".cpp", - ".c", - ".h", - ".php", - ".rb", - ".swift", - ".kt", - ".r", - ".jl", - ".go", - ".scala", - ".pl", - ".pm", - ".rs", - ".dart", - ".lua", - ".sh", - ".vb", - ".bat" - }); - private static final boolean DEFAULT_PROJECT_ENABLE = false; - private static final int DEFAULT_MAX_REVIEW_LINES = 1000; - private static final int DEFAULT_MAX_REVIEW_FILE_SIZE = 10000; - private static final boolean DEFAULT_ENABLED_VOTING = false; - private static final boolean DEFAULT_FILTER_NEGATIVE_COMMENTS = true; - private static final int DEFAULT_FILTER_COMMENTS_BELOW_SCORE = 0; - private static final boolean DEFAULT_FILTER_RELEVANT_COMMENTS = true; - private static final double DEFAULT_FILTER_COMMENTS_RELEVANCE_THRESHOLD = 0.6; - private static final int DEFAULT_VOTING_MIN_SCORE = -1; - private static final int DEFAULT_VOTING_MAX_SCORE = 1; - private static final boolean DEFAULT_INLINE_COMMENTS_AS_RESOLVED = false; - private static final boolean DEFAULT_PATCH_SET_COMMENTS_AS_RESOLVED = false; - private static final boolean DEFAULT_IGNORE_OUTDATED_INLINE_COMMENTS = false; - private static final boolean DEFAULT_IGNORE_RESOLVED_AI_CHAT_COMMENTS = true; - private static final boolean DEFAULT_FORCE_CREATE_ASSISTANT = false; - private static final boolean DEFAULT_ENABLE_MESSAGE_DEBUGGING = false; + private static final String DEFAULT_AI_MODE = "stateless"; + private static final boolean DEFAULT_REVIEW_PATCH_SET = true; + private static final boolean DEFAULT_REVIEW_COMMIT_MESSAGES = true; + private static final boolean DEFAULT_FULL_FILE_REVIEW = true; + private static final boolean DEFAULT_STREAM_OUTPUT = false; + private static final boolean DEFAULT_GLOBAL_ENABLE = false; + private static final String DEFAULT_DISABLED_USERS = ""; + private static final String DEFAULT_ENABLED_USERS = ENABLED_USERS_ALL; + private static final String DEFAULT_DISABLED_GROUPS = ""; + private static final String DEFAULT_ENABLED_GROUPS = ENABLED_GROUPS_ALL; + private static final String DEFAULT_DISABLED_TOPIC_FILTER = ""; + private static final String DEFAULT_ENABLED_TOPIC_FILTER = ENABLED_TOPICS_ALL; + private static final String DEFAULT_ENABLED_PROJECTS = ""; + private static final String DEFAULT_ENABLED_FILE_EXTENSIONS = + String.join( + ",", + new String[] { + ".py", ".java", ".js", ".ts", ".html", ".css", ".cs", ".cpp", ".c", ".h", ".php", ".rb", + ".swift", ".kt", ".r", ".jl", ".go", ".scala", ".pl", ".pm", ".rs", ".dart", ".lua", + ".sh", ".vb", ".bat" + }); + private static final boolean DEFAULT_PROJECT_ENABLE = false; + private static final int DEFAULT_MAX_REVIEW_LINES = 1000; + private static final int DEFAULT_MAX_REVIEW_FILE_SIZE = 10000; + private static final boolean DEFAULT_ENABLED_VOTING = false; + private static final boolean DEFAULT_FILTER_NEGATIVE_COMMENTS = true; + private static final int DEFAULT_FILTER_COMMENTS_BELOW_SCORE = 0; + private static final boolean DEFAULT_FILTER_RELEVANT_COMMENTS = true; + private static final double DEFAULT_FILTER_COMMENTS_RELEVANCE_THRESHOLD = 0.6; + private static final int DEFAULT_VOTING_MIN_SCORE = -1; + private static final int DEFAULT_VOTING_MAX_SCORE = 1; + private static final boolean DEFAULT_INLINE_COMMENTS_AS_RESOLVED = false; + private static final boolean DEFAULT_PATCH_SET_COMMENTS_AS_RESOLVED = false; + private static final boolean DEFAULT_IGNORE_OUTDATED_INLINE_COMMENTS = false; + private static final boolean DEFAULT_IGNORE_RESOLVED_AI_CHAT_COMMENTS = true; + private static final boolean DEFAULT_FORCE_CREATE_ASSISTANT = false; + private static final boolean DEFAULT_ENABLE_MESSAGE_DEBUGGING = false; - public static final String AUTH_HEADER_API_KEY = "api-key"; + public static final String AUTH_HEADER_API_KEY = "api-key"; - // Config setting keys - public static final String KEY_AI_SYSTEM_PROMPT = "aiSystemPrompt"; - public static final String KEY_AI_RELEVANCE_RULES = "aiRelevanceRules"; - public static final String KEY_AI_REVIEW_TEMPERATURE = "aiReviewTemperature"; - public static final String KEY_AI_COMMENT_TEMPERATURE = "aiCommentTemperature"; - public static final String KEY_VOTING_MIN_SCORE = "votingMinScore"; - public static final String KEY_VOTING_MAX_SCORE = "votingMaxScore"; - public static final String KEY_GERRIT_USERNAME = "gerritUserName"; + // Config setting keys + public static final String KEY_AI_SYSTEM_PROMPT = "aiSystemPrompt"; + public static final String KEY_AI_RELEVANCE_RULES = "aiRelevanceRules"; + public static final String KEY_AI_REVIEW_TEMPERATURE = "aiReviewTemperature"; + public static final String KEY_AI_COMMENT_TEMPERATURE = "aiCommentTemperature"; + public static final String KEY_VOTING_MIN_SCORE = "votingMinScore"; + public static final String KEY_VOTING_MAX_SCORE = "votingMaxScore"; + public static final String KEY_GERRIT_USERNAME = "gerritUserName"; - public static final String KEY_AI_TYPE = "aiType"; - public static final String KEY_AI_TOKEN = "aiToken"; - public static final String KEY_AI_DOMAIN = "aiDomain"; - public static final String KEY_AI_CHAT_ENDPOINT = "aiChatEndpoint"; - public static final String KEY_AI_AUTH_HEADER_NAME = "aiAuthHeaderName"; - private static final String KEY_AI_MODEL = "aiModel"; - public static final String KEY_STREAM_OUTPUT = "aiStreamOutput"; - private static final String KEY_AI_MODE = "aiMode"; - private static final String KEY_REVIEW_COMMIT_MESSAGES = "aiReviewCommitMessages"; - private static final String KEY_REVIEW_PATCH_SET = "aiReviewPatchSet"; - private static final String KEY_FULL_FILE_REVIEW = "gptFullFileReview"; - private static final String KEY_PROJECT_ENABLE = "isEnabled"; - private static final String KEY_GLOBAL_ENABLE = "globalEnable"; - private static final String KEY_DISABLED_USERS = "disabledUsers"; - private static final String KEY_ENABLED_USERS = "enabledUsers"; - private static final String KEY_DISABLED_GROUPS = "disabledGroups"; - private static final String KEY_ENABLED_GROUPS = "enabledGroups"; - private static final String KEY_DISABLED_TOPIC_FILTER = "disabledTopicFilter"; - private static final String KEY_ENABLED_TOPIC_FILTER = "enabledTopicFilter"; - private static final String KEY_ENABLED_PROJECTS = "enabledProjects"; - private static final String KEY_MAX_REVIEW_LINES = "maxReviewLines"; - private static final String KEY_MAX_REVIEW_FILE_SIZE = "maxReviewFileSize"; - private static final String KEY_ENABLED_FILE_EXTENSIONS = "enabledFileExtensions"; - private static final String KEY_ENABLED_VOTING = "enabledVoting"; - private static final String KEY_FILTER_NEGATIVE_COMMENTS = "filterNegativeComments"; - private static final String KEY_FILTER_COMMENTS_BELOW_SCORE = "filterCommentsBelowScore"; - private static final String KEY_FILTER_RELEVANT_COMMENTS = "filterRelevantComments"; - private static final String KEY_FILTER_COMMENTS_RELEVANCE_THRESHOLD = "filterCommentsRelevanceThreshold"; - private static final String KEY_INLINE_COMMENTS_AS_RESOLVED = "inlineCommentsAsResolved"; - private static final String KEY_PATCH_SET_COMMENTS_AS_RESOLVED = "patchSetCommentsAsResolved"; - private static final String KEY_IGNORE_OUTDATED_INLINE_COMMENTS = "ignoreOutdatedInlineComments"; - private static final String KEY_IGNORE_RESOLVED_AI_CHAT_COMMENTS = "ignoreResolvedChatGptComments"; - private static final String KEY_FORCE_CREATE_ASSISTANT = "forceCreateAssistant"; - private static final String KEY_ENABLE_MESSAGE_DEBUGGING = "enableMessageDebugging"; + public static final String KEY_AI_TYPE = "aiType"; + public static final String KEY_AI_TOKEN = "aiToken"; + public static final String KEY_AI_DOMAIN = "aiDomain"; + public static final String KEY_AI_CHAT_ENDPOINT = "aiChatEndpoint"; + public static final String KEY_AI_AUTH_HEADER_NAME = "aiAuthHeaderName"; + private static final String KEY_AI_MODEL = "aiModel"; + public static final String KEY_STREAM_OUTPUT = "aiStreamOutput"; + private static final String KEY_AI_MODE = "aiMode"; + private static final String KEY_REVIEW_COMMIT_MESSAGES = "aiReviewCommitMessages"; + private static final String KEY_REVIEW_PATCH_SET = "aiReviewPatchSet"; + private static final String KEY_FULL_FILE_REVIEW = "gptFullFileReview"; + private static final String KEY_PROJECT_ENABLE = "isEnabled"; + private static final String KEY_GLOBAL_ENABLE = "globalEnable"; + private static final String KEY_DISABLED_USERS = "disabledUsers"; + private static final String KEY_ENABLED_USERS = "enabledUsers"; + private static final String KEY_DISABLED_GROUPS = "disabledGroups"; + private static final String KEY_ENABLED_GROUPS = "enabledGroups"; + private static final String KEY_DISABLED_TOPIC_FILTER = "disabledTopicFilter"; + private static final String KEY_ENABLED_TOPIC_FILTER = "enabledTopicFilter"; + private static final String KEY_ENABLED_PROJECTS = "enabledProjects"; + private static final String KEY_MAX_REVIEW_LINES = "maxReviewLines"; + private static final String KEY_MAX_REVIEW_FILE_SIZE = "maxReviewFileSize"; + private static final String KEY_ENABLED_FILE_EXTENSIONS = "enabledFileExtensions"; + private static final String KEY_ENABLED_VOTING = "enabledVoting"; + private static final String KEY_FILTER_NEGATIVE_COMMENTS = "filterNegativeComments"; + private static final String KEY_FILTER_COMMENTS_BELOW_SCORE = "filterCommentsBelowScore"; + private static final String KEY_FILTER_RELEVANT_COMMENTS = "filterRelevantComments"; + private static final String KEY_FILTER_COMMENTS_RELEVANCE_THRESHOLD = + "filterCommentsRelevanceThreshold"; + private static final String KEY_INLINE_COMMENTS_AS_RESOLVED = "inlineCommentsAsResolved"; + private static final String KEY_PATCH_SET_COMMENTS_AS_RESOLVED = "patchSetCommentsAsResolved"; + private static final String KEY_IGNORE_OUTDATED_INLINE_COMMENTS = "ignoreOutdatedInlineComments"; + private static final String KEY_IGNORE_RESOLVED_AI_CHAT_COMMENTS = + "ignoreResolvedChatGptComments"; + private static final String KEY_FORCE_CREATE_ASSISTANT = "forceCreateAssistant"; + private static final String KEY_ENABLE_MESSAGE_DEBUGGING = "enableMessageDebugging"; - private final OneOffRequestContext context; - @Getter - private final Account.Id userId; - @Getter - private final PluginConfig globalConfig; - @Getter - private final PluginConfig projectConfig; - @Getter - private final String gerritUserEmail; - @Getter - private final GerritApi gerritApi; + private final OneOffRequestContext context; + @Getter private final Account.Id userId; + @Getter private final PluginConfig globalConfig; + @Getter private final PluginConfig projectConfig; + @Getter private final String gerritUserEmail; + @Getter private final GerritApi gerritApi; + public Configuration( + OneOffRequestContext context, + GerritApi gerritApi, + PluginConfig globalConfig, + PluginConfig projectConfig, + String gerritUserEmail, + Account.Id userId) { + this.context = context; + this.gerritApi = gerritApi; + this.globalConfig = globalConfig; + this.projectConfig = projectConfig; + this.gerritUserEmail = gerritUserEmail; + this.userId = userId; + } - public Configuration(OneOffRequestContext context, GerritApi gerritApi, PluginConfig globalConfig, PluginConfig projectConfig, String gerritUserEmail, Account.Id userId) { - this.context = context; - this.gerritApi = gerritApi; - this.globalConfig = globalConfig; - this.projectConfig = projectConfig; - this.gerritUserEmail = gerritUserEmail; - this.userId = userId; + public ManualRequestContext openRequestContext() { + return context.openAs(userId); + } + + public String getAIToken() { + // The Aitoken isn't required for all aiTypes. + + return getValidatedOrThrow(KEY_AI_TOKEN); + } + + public String getGerritUserName() { + return getValidatedOrThrow(KEY_GERRIT_USERNAME); + } + + public String getAIDomain() { + // AiDomain is not a required field UNLESS we are using OLLAMA which is a local / private based + // instance by its very nature. it makes more sense to enforce that it is a required field for + // when + // aiType==ollama. + String aiDomain = + getAIType() == AIType.OLLAMA + ? getValidatedOrThrow(KEY_AI_DOMAIN) + : getString(KEY_AI_DOMAIN, OPENAI_DOMAIN); + + // trim end slash, so putting endpoint urls together is easier. + return aiDomain.endsWith("/") ? aiDomain.substring(0, aiDomain.length() - 1) : aiDomain; + } + + public String getAIModel() { + // default to the chatGPT model if nothing is specified, excecpt if we are using + // an openAI compliant service like OLLAMA, then we use its appropriate default. + return getString( + KEY_AI_MODEL, getAIType() == AIType.OLLAMA ? DEFAULT_OPENAI_MODEL : DEFAULT_CHATGPT_MODEL); + } + + public boolean getAIReviewPatchSet() { + return getBoolean(KEY_REVIEW_PATCH_SET, DEFAULT_REVIEW_PATCH_SET); + } + + public Modes getAIMode() { + String mode = getString(KEY_AI_MODE, DEFAULT_AI_MODE); + return getValueAsEnum(Modes.class, mode); + } + + public AIType getAIType() { + // return default type of CHATGPT if no value has been specified. + // Leaving the default behaviour of this plugin as it was historically. + String aiType = getString(KEY_AI_TYPE, "CHATGPT"); + // for ease of use with enum, use toUpper, so we can always be case-sensitive on compares. + return getValueAsEnum(AIType.class, aiType.toUpperCase()); + } + + public String getChatEndpoint() { + // optional, and only used when combined with the "GENERIC" aiType, for testing + // of new or not yet supported ai frameworks. + return getString(KEY_AI_CHAT_ENDPOINT, ""); + } + + public String getAuthHeaderName() { + // optional, and only used when combined with the "GENERIC" aiType, for testing + // of new or not yet supported ai frameworks. + return getString(KEY_AI_AUTH_HEADER_NAME, ""); + } + + public boolean getAIReviewCommitMessages() { + return getBoolean(KEY_REVIEW_COMMIT_MESSAGES, DEFAULT_REVIEW_COMMIT_MESSAGES); + } + + public boolean getGptFullFileReview() { + return getBoolean(KEY_FULL_FILE_REVIEW, DEFAULT_FULL_FILE_REVIEW); + } + + public boolean getAIStreamOutput() { + return getBoolean(KEY_STREAM_OUTPUT, DEFAULT_STREAM_OUTPUT); + } + + public boolean isProjectEnable() { + return projectConfig.getBoolean(KEY_PROJECT_ENABLE, DEFAULT_PROJECT_ENABLE); + } + + public boolean isGlobalEnable() { + return globalConfig.getBoolean(KEY_GLOBAL_ENABLE, DEFAULT_GLOBAL_ENABLE); + } + + public List<String> getDisabledUsers() { + return splitConfig(globalConfig.getString(KEY_DISABLED_USERS, DEFAULT_DISABLED_USERS)); + } + + public List<String> getEnabledUsers() { + return splitConfig(globalConfig.getString(KEY_ENABLED_USERS, DEFAULT_ENABLED_USERS)); + } + + public List<String> getDisabledGroups() { + return splitConfig(globalConfig.getString(KEY_DISABLED_GROUPS, DEFAULT_DISABLED_GROUPS)); + } + + public List<String> getEnabledGroups() { + return splitConfig(globalConfig.getString(KEY_ENABLED_GROUPS, DEFAULT_ENABLED_GROUPS)); + } + + public List<String> getDisabledTopicFilter() { + return splitConfig( + globalConfig.getString(KEY_DISABLED_TOPIC_FILTER, DEFAULT_DISABLED_TOPIC_FILTER)); + } + + public List<String> getEnabledTopicFilter() { + return splitConfig( + globalConfig.getString(KEY_ENABLED_TOPIC_FILTER, DEFAULT_ENABLED_TOPIC_FILTER)); + } + + public String getEnabledProjects() { + return globalConfig.getString(KEY_ENABLED_PROJECTS, DEFAULT_ENABLED_PROJECTS); + } + + public int getMaxReviewLines() { + return getInt(KEY_MAX_REVIEW_LINES, DEFAULT_MAX_REVIEW_LINES); + } + + public int getMaxReviewFileSize() { + return getInt(KEY_MAX_REVIEW_FILE_SIZE, DEFAULT_MAX_REVIEW_FILE_SIZE); + } + + public List<String> getEnabledFileExtensions() { + return splitConfig( + globalConfig.getString(KEY_ENABLED_FILE_EXTENSIONS, DEFAULT_ENABLED_FILE_EXTENSIONS)); + } + + public boolean isVotingEnabled() { + return getBoolean(KEY_ENABLED_VOTING, DEFAULT_ENABLED_VOTING); + } + + public boolean getFilterNegativeComments() { + return getBoolean(KEY_FILTER_NEGATIVE_COMMENTS, DEFAULT_FILTER_NEGATIVE_COMMENTS); + } + + public int getFilterCommentsBelowScore() { + return getInt(KEY_FILTER_COMMENTS_BELOW_SCORE, DEFAULT_FILTER_COMMENTS_BELOW_SCORE); + } + + public boolean getFilterRelevantComments() { + return getBoolean(KEY_FILTER_RELEVANT_COMMENTS, DEFAULT_FILTER_RELEVANT_COMMENTS); + } + + public double getFilterCommentsRelevanceThreshold() { + return getDouble( + KEY_FILTER_COMMENTS_RELEVANCE_THRESHOLD, DEFAULT_FILTER_COMMENTS_RELEVANCE_THRESHOLD); + } + + public Locale getLocaleDefault() { + return Locale.getDefault(); + } + + public int getVotingMinScore() { + return getInt(KEY_VOTING_MIN_SCORE, DEFAULT_VOTING_MIN_SCORE); + } + + public int getVotingMaxScore() { + return getInt(KEY_VOTING_MAX_SCORE, DEFAULT_VOTING_MAX_SCORE); + } + + public boolean getInlineCommentsAsResolved() { + return getBoolean(KEY_INLINE_COMMENTS_AS_RESOLVED, DEFAULT_INLINE_COMMENTS_AS_RESOLVED); + } + + public boolean getPatchSetCommentsAsResolved() { + return getBoolean(KEY_PATCH_SET_COMMENTS_AS_RESOLVED, DEFAULT_PATCH_SET_COMMENTS_AS_RESOLVED); + } + + public boolean getIgnoreResolvedAIChatComments() { + return getBoolean( + KEY_IGNORE_RESOLVED_AI_CHAT_COMMENTS, DEFAULT_IGNORE_RESOLVED_AI_CHAT_COMMENTS); + } + + public boolean getForceCreateAssistant() { + return getBoolean(KEY_FORCE_CREATE_ASSISTANT, DEFAULT_FORCE_CREATE_ASSISTANT); + } + + public boolean getEnableMessageDebugging() { + return getBoolean(KEY_ENABLE_MESSAGE_DEBUGGING, DEFAULT_ENABLE_MESSAGE_DEBUGGING); + } + + public boolean getIgnoreOutdatedInlineComments() { + return getBoolean(KEY_IGNORE_OUTDATED_INLINE_COMMENTS, DEFAULT_IGNORE_OUTDATED_INLINE_COMMENTS); + } + + public NameValuePair getAuthorizationHeaderInfo() { + switch (getAIType()) { + case AZUREOPENAI: + return new BasicNameValuePair(AUTH_HEADER_API_KEY, getAIToken()); + case OLLAMA: + case GENERIC: + // by default no auth header is required for ollama so return null for no auth. + // But if they wish to add some auth requirements, maybe for hosted setup, + // then allow the header to be generically specified, same as the GENERIC configuration. + return !Strings.isNullOrEmpty(getAuthHeaderName()) + ? new BasicNameValuePair(getAuthHeaderName(), getAIToken()) + : null; + case CHATGPT: + default: + // by default, or for chatGpt use bearer token, if someone is adding a new aiType, it can + // fall + // into this block - or they will need to extend the cases above. + return new BasicNameValuePair(HttpHeaders.AUTHORIZATION, "Bearer " + getAIToken()); } + } - public ManualRequestContext openRequestContext() { - return context.openAs(userId); + public String getString(String key, String defaultValue) { + String value = projectConfig.getString(key); + if (value != null) { + return value; } + return globalConfig.getString(key, defaultValue); + } - public String getAIToken() { - // The Aitoken isn't required for all aiTypes. - - return getValidatedOrThrow(KEY_AI_TOKEN); + private String getValidatedOrThrow(String key) { + String value = projectConfig.getString(key); + if (value == null) { + value = globalConfig.getString(key); } - - public String getGerritUserName() { - return getValidatedOrThrow(KEY_GERRIT_USERNAME); + if (value == null) { + throw new RuntimeException(String.format(NOT_CONFIGURED_ERROR_MSG, key)); } + return value; + } - public String getAIDomain() { - // AiDomain is not a required field UNLESS we are using OLLAMA which is a local / private based - // instance by its very nature. it makes more sense to enforce that it is a required field for when - // aiType==ollama. - String aiDomain = getAIType() == AIType.OLLAMA ? - getValidatedOrThrow(KEY_AI_DOMAIN) : - getString(KEY_AI_DOMAIN, OPENAI_DOMAIN); + private int getInt(String key, int defaultValue) { + int valueForProject = projectConfig.getInt(key, defaultValue); + // To avoid misinterpreting an undefined value as zero, a secondary check is performed by + // retrieving the value + // as a String. + if (valueForProject != defaultValue + && valueForProject != 0 + && projectConfig.getString(key, "") != null) { + return valueForProject; + } + return globalConfig.getInt(key, defaultValue); + } - // trim end slash, so putting endpoint urls together is easier. - return aiDomain.endsWith("/") ? - aiDomain.substring(0, aiDomain.length() - 1) : aiDomain; + private boolean getBoolean(String key, boolean defaultValue) { + boolean valueForProject = projectConfig.getBoolean(key, defaultValue); + if (projectConfig.getString(key) != null) { + return valueForProject; } + return globalConfig.getBoolean(key, defaultValue); + } - public String getAIModel() { - // default to the chatGPT model if nothing is specified, excecpt if we are using - // an openAI compliant service like OLLAMA, then we use its appropriate default. - return getString(KEY_AI_MODEL, - getAIType() == AIType.OLLAMA ? DEFAULT_OPENAI_MODEL : DEFAULT_CHATGPT_MODEL); - } + private Double getDouble(String key, Double defaultValue) { + return Double.parseDouble(getString(key, String.valueOf(defaultValue))); + } - public boolean getAIReviewPatchSet() { - return getBoolean(KEY_REVIEW_PATCH_SET, DEFAULT_REVIEW_PATCH_SET); + @NotNull + private static <T extends Enum<T>> T getValueAsEnum(Class<T> enumClass, String value) { + try { + return Enum.valueOf(enumClass, value); + } catch (IllegalArgumentException e) { + throw new RuntimeException( + String.format("Illegal value: %s for enum class: %s", value, enumClass), e); } + } - public Modes getAIMode() { - String mode = getString(KEY_AI_MODE, DEFAULT_AI_MODE); - return getValueAsEnum(Modes.class, mode); - } - public AIType getAIType(){ - // return default type of CHATGPT if no value has been specified. - // Leaving the default behaviour of this plugin as it was historically. - String aiType = getString(KEY_AI_TYPE, "CHATGPT"); - // for ease of use with enum, use toUpper, so we can always be case-sensitive on compares. - return getValueAsEnum( AIType.class, aiType.toUpperCase()); - } - - public String getChatEndpoint(){ - // optional, and only used when combined with the "GENERIC" aiType, for testing - // of new or not yet supported ai frameworks. - return getString(KEY_AI_CHAT_ENDPOINT, ""); - } - public String getAuthHeaderName(){ - // optional, and only used when combined with the "GENERIC" aiType, for testing - // of new or not yet supported ai frameworks. - return getString(KEY_AI_AUTH_HEADER_NAME, ""); - } - - public boolean getAIReviewCommitMessages() { - return getBoolean(KEY_REVIEW_COMMIT_MESSAGES, DEFAULT_REVIEW_COMMIT_MESSAGES); - } - - public boolean getGptFullFileReview() { - return getBoolean(KEY_FULL_FILE_REVIEW, DEFAULT_FULL_FILE_REVIEW); - } - - public boolean getAIStreamOutput() { - return getBoolean(KEY_STREAM_OUTPUT, DEFAULT_STREAM_OUTPUT); - } - - public boolean isProjectEnable() { - return projectConfig.getBoolean(KEY_PROJECT_ENABLE, DEFAULT_PROJECT_ENABLE); - } - - public boolean isGlobalEnable() { - return globalConfig.getBoolean(KEY_GLOBAL_ENABLE, DEFAULT_GLOBAL_ENABLE); - } - - public List<String> getDisabledUsers() { - return splitConfig(globalConfig.getString(KEY_DISABLED_USERS, DEFAULT_DISABLED_USERS)); - } - - public List<String> getEnabledUsers() { - return splitConfig(globalConfig.getString(KEY_ENABLED_USERS, DEFAULT_ENABLED_USERS)); - } - - public List<String> getDisabledGroups() { - return splitConfig(globalConfig.getString(KEY_DISABLED_GROUPS, DEFAULT_DISABLED_GROUPS)); - } - - public List<String> getEnabledGroups() { - return splitConfig(globalConfig.getString(KEY_ENABLED_GROUPS, DEFAULT_ENABLED_GROUPS)); - } - - public List<String> getDisabledTopicFilter() { - return splitConfig(globalConfig.getString(KEY_DISABLED_TOPIC_FILTER, DEFAULT_DISABLED_TOPIC_FILTER)); - } - - public List<String> getEnabledTopicFilter() { - return splitConfig(globalConfig.getString(KEY_ENABLED_TOPIC_FILTER, DEFAULT_ENABLED_TOPIC_FILTER)); - } - - public String getEnabledProjects() { - return globalConfig.getString(KEY_ENABLED_PROJECTS, DEFAULT_ENABLED_PROJECTS); - } - - public int getMaxReviewLines() { - return getInt(KEY_MAX_REVIEW_LINES, DEFAULT_MAX_REVIEW_LINES); - } - - public int getMaxReviewFileSize() { - return getInt(KEY_MAX_REVIEW_FILE_SIZE, DEFAULT_MAX_REVIEW_FILE_SIZE); - } - - public List<String> getEnabledFileExtensions() { - return splitConfig(globalConfig.getString(KEY_ENABLED_FILE_EXTENSIONS, DEFAULT_ENABLED_FILE_EXTENSIONS)); - } - - public boolean isVotingEnabled() { - return getBoolean(KEY_ENABLED_VOTING, DEFAULT_ENABLED_VOTING); - } - - public boolean getFilterNegativeComments() { - return getBoolean(KEY_FILTER_NEGATIVE_COMMENTS, DEFAULT_FILTER_NEGATIVE_COMMENTS); - } - - public int getFilterCommentsBelowScore() { - return getInt(KEY_FILTER_COMMENTS_BELOW_SCORE, DEFAULT_FILTER_COMMENTS_BELOW_SCORE); - } - - public boolean getFilterRelevantComments() { - return getBoolean(KEY_FILTER_RELEVANT_COMMENTS, DEFAULT_FILTER_RELEVANT_COMMENTS); - } - - public double getFilterCommentsRelevanceThreshold() { - return getDouble(KEY_FILTER_COMMENTS_RELEVANCE_THRESHOLD, DEFAULT_FILTER_COMMENTS_RELEVANCE_THRESHOLD); - } - - public Locale getLocaleDefault() { - return Locale.getDefault(); - } - - public int getVotingMinScore() { - return getInt(KEY_VOTING_MIN_SCORE, DEFAULT_VOTING_MIN_SCORE); - } - - public int getVotingMaxScore() { - return getInt(KEY_VOTING_MAX_SCORE, DEFAULT_VOTING_MAX_SCORE); - } - - public boolean getInlineCommentsAsResolved() { - return getBoolean(KEY_INLINE_COMMENTS_AS_RESOLVED, DEFAULT_INLINE_COMMENTS_AS_RESOLVED); - } - - public boolean getPatchSetCommentsAsResolved() { - return getBoolean(KEY_PATCH_SET_COMMENTS_AS_RESOLVED, DEFAULT_PATCH_SET_COMMENTS_AS_RESOLVED); - } - - public boolean getIgnoreResolvedAIChatComments() { - return getBoolean(KEY_IGNORE_RESOLVED_AI_CHAT_COMMENTS, DEFAULT_IGNORE_RESOLVED_AI_CHAT_COMMENTS); - } - - public boolean getForceCreateAssistant() { - return getBoolean(KEY_FORCE_CREATE_ASSISTANT, DEFAULT_FORCE_CREATE_ASSISTANT); - } - - public boolean getEnableMessageDebugging() { - return getBoolean(KEY_ENABLE_MESSAGE_DEBUGGING, DEFAULT_ENABLE_MESSAGE_DEBUGGING); - } - - public boolean getIgnoreOutdatedInlineComments() { - return getBoolean(KEY_IGNORE_OUTDATED_INLINE_COMMENTS, DEFAULT_IGNORE_OUTDATED_INLINE_COMMENTS); - } - - - public NameValuePair getAuthorizationHeaderInfo() { - switch (getAIType()) { - case AZUREOPENAI: - return new BasicNameValuePair(AUTH_HEADER_API_KEY, getAIToken()); - case OLLAMA: - case GENERIC: - // by default no auth header is required for ollama so return null for no auth. - // But if they wish to add some auth requirements, maybe for hosted setup, - // then allow the header to be generically specified, same as the GENERIC configuration. - return !Strings.isNullOrEmpty(getAuthHeaderName()) ? - new BasicNameValuePair(getAuthHeaderName(), getAIToken()) : null; - case CHATGPT: - default: - // by default, or for chatGpt use bearer token, if someone is adding a new aiType, it can fall - // into this block - or they will need to extend the cases above. - return new BasicNameValuePair(HttpHeaders.AUTHORIZATION, "Bearer " + getAIToken()); - } - } - - public String getString(String key, String defaultValue) { - String value = projectConfig.getString(key); - if (value != null) { - return value; - } - return globalConfig.getString(key, defaultValue); - } - - private String getValidatedOrThrow(String key) { - String value = projectConfig.getString(key); - if (value == null) { - value = globalConfig.getString(key); - } - if (value == null) { - throw new RuntimeException(String.format(NOT_CONFIGURED_ERROR_MSG, key)); - } - return value; - } - - private int getInt(String key, int defaultValue) { - int valueForProject = projectConfig.getInt(key, defaultValue); - // To avoid misinterpreting an undefined value as zero, a secondary check is performed by retrieving the value - // as a String. - if (valueForProject != defaultValue && valueForProject != 0 - && projectConfig.getString(key, "") != null) { - return valueForProject; - } - return globalConfig.getInt(key, defaultValue); - } - - private boolean getBoolean(String key, boolean defaultValue) { - boolean valueForProject = projectConfig.getBoolean(key, defaultValue); - if (projectConfig.getString(key) != null) { - return valueForProject; - } - return globalConfig.getBoolean(key, defaultValue); - } - - private Double getDouble(String key, Double defaultValue) { - return Double.parseDouble(getString(key, String.valueOf(defaultValue))); - } - - @NotNull - private static <T extends Enum<T>> T getValueAsEnum(Class<T> enumClass, String value) { - try { - return Enum.valueOf(enumClass, value); - } catch (IllegalArgumentException e) { - throw new RuntimeException(String.format("Illegal value: %s for enum class: %s", value, enumClass), e); - } - } - - private List<String> splitConfig(String value) { - Pattern separator=Pattern.compile("\\s*,\\s*"); - return Arrays.asList(separator.split(value)); - } + private List<String> splitConfig(String value) { + Pattern separator = Pattern.compile("\\s*,\\s*"); + return Arrays.asList(separator.split(value)); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/DynamicConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/DynamicConfiguration.java index 398da82..34d40de 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/DynamicConfiguration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/config/DynamicConfiguration.java
@@ -2,47 +2,47 @@ import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - import java.util.HashMap; import java.util.Map; import java.util.Optional; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; @Slf4j public class DynamicConfiguration { - public static final String KEY_DYNAMIC_CONFIG = "dynamicConfig"; + public static final String KEY_DYNAMIC_CONFIG = "dynamicConfig"; - private final PluginDataHandler pluginDataHandler; - @Getter - private final Map<String, String> dynamicConfig; + private final PluginDataHandler pluginDataHandler; + @Getter private final Map<String, String> dynamicConfig; - public DynamicConfiguration(PluginDataHandlerProvider pluginDataHandlerProvider) { - this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope(); - dynamicConfig = Optional.ofNullable(pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class)) - .orElse(new HashMap<>()); + public DynamicConfiguration(PluginDataHandlerProvider pluginDataHandlerProvider) { + this.pluginDataHandler = pluginDataHandlerProvider.getChangeScope(); + dynamicConfig = + Optional.ofNullable(pluginDataHandler.getJsonValue(KEY_DYNAMIC_CONFIG, String.class)) + .orElse(new HashMap<>()); + } + + public void setConfig(String key, String value) { + dynamicConfig.put(key, value); + } + + public void updateConfiguration(boolean modifiedDynamicConfig, boolean shouldResetDynamicConfig) { + if (dynamicConfig == null || dynamicConfig.isEmpty()) return; + if (shouldResetDynamicConfig && !modifiedDynamicConfig) { + pluginDataHandler.removeValue(KEY_DYNAMIC_CONFIG); + } else { + if (shouldResetDynamicConfig) { + resetDynamicConfig(); + } + log.info("Updating dynamic configuration with {}", dynamicConfig); + pluginDataHandler.setJsonValue(KEY_DYNAMIC_CONFIG, dynamicConfig); } + } - public void setConfig(String key, String value) { - dynamicConfig.put(key, value); - } - - public void updateConfiguration(boolean modifiedDynamicConfig, boolean shouldResetDynamicConfig) { - if (dynamicConfig == null || dynamicConfig.isEmpty()) return; - if (shouldResetDynamicConfig && !modifiedDynamicConfig) { - pluginDataHandler.removeValue(KEY_DYNAMIC_CONFIG); - } - else { - if (shouldResetDynamicConfig) { - resetDynamicConfig(); - } - log.info("Updating dynamic configuration with {}", dynamicConfig); - pluginDataHandler.setJsonValue(KEY_DYNAMIC_CONFIG, dynamicConfig); - } - } - - private void resetDynamicConfig() { - // The keys with empty values are simply removed - dynamicConfig.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty()); - } + private void resetDynamicConfig() { + // The keys with empty values are simply removed + dynamicConfig + .entrySet() + .removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty()); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataHandler.java index af6bb68..5dbd33f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataHandler.java
@@ -8,43 +8,38 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPermittedVotingRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.HashSet; +import lombok.extern.slf4j.Slf4j; @Slf4j public class ChangeSetDataHandler { - public static void update( - Configuration config, - GerritChange change, - GerritClient gerritClient, - ChangeSetData changeSetData, - Localizer localizer - ) { - GerritClientData gerritClientData = gerritClient.getClientData(change); - AIChatDataPrompt AIChatDataPrompt = new AIChatDataPrompt( - config, - changeSetData, - change, - gerritClientData, - localizer); + public static void update( + Configuration config, + GerritChange change, + GerritClient gerritClient, + ChangeSetData changeSetData, + Localizer localizer) { + GerritClientData gerritClientData = gerritClient.getClientData(change); + AIChatDataPrompt AIChatDataPrompt = + new AIChatDataPrompt(config, changeSetData, change, gerritClientData, localizer); - changeSetData.setCommentPropertiesSize(gerritClientData.getCommentProperties().size()); - changeSetData.setDirectives(new HashSet<>()); - changeSetData.setReviewSystemMessage(null); - changeSetData.setReviewAIDataPrompt(AIChatDataPrompt.buildPrompt()); - if (config.isVotingEnabled() && !change.getIsCommentEvent()) { - GerritPermittedVotingRange permittedVotingRange = gerritClient.getPermittedVotingRange(change); - if (permittedVotingRange != null) { - if (permittedVotingRange.getMin() > config.getVotingMinScore()) { - log.debug("Minimum ChatGPT voting score set to {}", permittedVotingRange.getMin()); - changeSetData.setVotingMinScore(permittedVotingRange.getMin()); - } - if (permittedVotingRange.getMax() < config.getVotingMaxScore()) { - log.debug("Maximum ChatGPT voting score set to {}", permittedVotingRange.getMax()); - changeSetData.setVotingMaxScore(permittedVotingRange.getMax()); - } - } + changeSetData.setCommentPropertiesSize(gerritClientData.getCommentProperties().size()); + changeSetData.setDirectives(new HashSet<>()); + changeSetData.setReviewSystemMessage(null); + changeSetData.setReviewAIDataPrompt(AIChatDataPrompt.buildPrompt()); + if (config.isVotingEnabled() && !change.getIsCommentEvent()) { + GerritPermittedVotingRange permittedVotingRange = + gerritClient.getPermittedVotingRange(change); + if (permittedVotingRange != null) { + if (permittedVotingRange.getMin() > config.getVotingMinScore()) { + log.debug("Minimum ChatGPT voting score set to {}", permittedVotingRange.getMin()); + changeSetData.setVotingMinScore(permittedVotingRange.getMin()); } + if (permittedVotingRange.getMax() < config.getVotingMaxScore()) { + log.debug("Maximum ChatGPT voting score set to {}", permittedVotingRange.getMax()); + changeSetData.setVotingMaxScore(permittedVotingRange.getMax()); + } + } } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataProvider.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataProvider.java index 616aec4..c18a63b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataProvider.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/ChangeSetDataProvider.java
@@ -7,17 +7,18 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; public class ChangeSetDataProvider implements Provider<ChangeSetData> { - private final int gptAccountId; - private final Configuration config; + private final int gptAccountId; + private final Configuration config; - @Inject - ChangeSetDataProvider(Configuration config, AccountCache accountCache) { - this.config = config; - this.gptAccountId = accountCache.getByUsername(config.getGerritUserName()).get().account().id().get(); - } + @Inject + ChangeSetDataProvider(Configuration config, AccountCache accountCache) { + this.config = config; + this.gptAccountId = + accountCache.getByUsername(config.getGerritUserName()).get().account().id().get(); + } - @Override - public ChangeSetData get() { - return new ChangeSetData(gptAccountId, config.getVotingMinScore(), config.getVotingMaxScore()); - } + @Override + public ChangeSetData get() { + return new ChangeSetData(gptAccountId, config.getVotingMinScore(), config.getVotingMaxScore()); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandler.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandler.java index 59e6474..facef4b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandler.java
@@ -1,9 +1,10 @@ package com.googlesource.gerrit.plugins.aicodereview.data; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; import com.google.inject.Singleton; - import java.io.IOException; import java.lang.reflect.Type; import java.nio.file.Files; @@ -11,74 +12,71 @@ import java.util.Map; import java.util.Properties; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; - @Singleton public class PluginDataHandler { - private final Path configFile; - private final Properties configProperties = new Properties(); + private final Path configFile; + private final Properties configProperties = new Properties(); - @Inject - public PluginDataHandler(Path configFilePath) { - this.configFile = configFilePath; - try { - loadOrCreateProperties(); - } catch (IOException e) { - throw new RuntimeException(e); - } + @Inject + public PluginDataHandler(Path configFilePath) { + this.configFile = configFilePath; + try { + loadOrCreateProperties(); + } catch (IOException e) { + throw new RuntimeException(e); } + } - public synchronized void setValue(String key, String value) { - configProperties.setProperty(key, value); - storeProperties(); - } + public synchronized void setValue(String key, String value) { + configProperties.setProperty(key, value); + storeProperties(); + } - public synchronized void setJsonValue(String key, Object value) { - setValue(key, getGson().toJson(value)); - } + public synchronized void setJsonValue(String key, Object value) { + setValue(key, getGson().toJson(value)); + } - public String getValue(String key) { - return configProperties.getProperty(key); - } + public String getValue(String key) { + return configProperties.getProperty(key); + } - public <T> Map<String, T> getJsonValue(String key, Class<T> clazz) { - String value = getValue(key); - if (value == null || value.isEmpty()) return null; - Type typeOfMap = TypeToken.getParameterized(Map.class, String.class, clazz).getType(); - return getGson().fromJson(value, typeOfMap); - } + public <T> Map<String, T> getJsonValue(String key, Class<T> clazz) { + String value = getValue(key); + if (value == null || value.isEmpty()) return null; + Type typeOfMap = TypeToken.getParameterized(Map.class, String.class, clazz).getType(); + return getGson().fromJson(value, typeOfMap); + } - public synchronized void removeValue(String key) { - if (configProperties.containsKey(key)) { - configProperties.remove(key); - storeProperties(); - } + public synchronized void removeValue(String key) { + if (configProperties.containsKey(key)) { + configProperties.remove(key); + storeProperties(); } + } - public synchronized void destroy() { - try { - Files.deleteIfExists(configFile); - } catch (IOException e) { - throw new RuntimeException("Failed to delete the config file: " + configFile, e); - } + public synchronized void destroy() { + try { + Files.deleteIfExists(configFile); + } catch (IOException e) { + throw new RuntimeException("Failed to delete the config file: " + configFile, e); } + } - private void storeProperties() { - try (var output = Files.newOutputStream(configFile)) { - configProperties.store(output, null); - } - catch (IOException e) { - throw new RuntimeException(e); - } + private void storeProperties() { + try (var output = Files.newOutputStream(configFile)) { + configProperties.store(output, null); + } catch (IOException e) { + throw new RuntimeException(e); } + } - private void loadOrCreateProperties() throws IOException { - if (Files.notExists(configFile)) { - Files.createFile(configFile); - } else { - try (var input = Files.newInputStream(configFile)) { - configProperties.load(input); - } - } + private void loadOrCreateProperties() throws IOException { + if (Files.notExists(configFile)) { + Files.createFile(configFile); + } else { + try (var input = Files.newInputStream(configFile)) { + configProperties.load(input); + } } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerBaseProvider.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerBaseProvider.java index 7d4ca18..74b04eb 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerBaseProvider.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerBaseProvider.java
@@ -3,27 +3,27 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; - import java.nio.file.Path; @Singleton public class PluginDataHandlerBaseProvider implements Provider<PluginDataHandler> { - private static final String PATH_SUFFIX = ".data"; - private static final String PATH_GLOBAL = "global"; + private static final String PATH_SUFFIX = ".data"; + private static final String PATH_GLOBAL = "global"; - private final Path defaultPluginDataPath; + private final Path defaultPluginDataPath; - @Inject - public PluginDataHandlerBaseProvider(@com.google.gerrit.extensions.annotations.PluginData Path defaultPluginDataPath) { - this.defaultPluginDataPath = defaultPluginDataPath; - } + @Inject + public PluginDataHandlerBaseProvider( + @com.google.gerrit.extensions.annotations.PluginData Path defaultPluginDataPath) { + this.defaultPluginDataPath = defaultPluginDataPath; + } - public PluginDataHandler get(String path) { - return new PluginDataHandler(defaultPluginDataPath.resolve(path + PATH_SUFFIX)); - } + public PluginDataHandler get(String path) { + return new PluginDataHandler(defaultPluginDataPath.resolve(path + PATH_SUFFIX)); + } - @Override - public PluginDataHandler get() { - return get(PATH_GLOBAL); - } + @Override + public PluginDataHandler get() { + return get(PATH_GLOBAL); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerProvider.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerProvider.java index 7c7d855..a3b7425 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerProvider.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/data/PluginDataHandlerProvider.java
@@ -1,46 +1,45 @@ package com.googlesource.gerrit.plugins.aicodereview.data; +import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.sanitizeFilename; + import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; - import java.nio.file.Path; -import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.sanitizeFilename; - @Singleton -public class PluginDataHandlerProvider extends PluginDataHandlerBaseProvider implements Provider<PluginDataHandler> { - private static final String PATH_ASSISTANTS = ".assistants"; +public class PluginDataHandlerProvider extends PluginDataHandlerBaseProvider + implements Provider<PluginDataHandler> { + private static final String PATH_ASSISTANTS = ".assistants"; - private final String projectName; - private final String changeKey; - private final String assistantsWorkspace; + private final String projectName; + private final String changeKey; + private final String assistantsWorkspace; - @Inject - public PluginDataHandlerProvider( - @com.google.gerrit.extensions.annotations.PluginData Path defaultPluginDataPath, - GerritChange change - ) { - super(defaultPluginDataPath); - projectName = sanitizeFilename(change.getProjectName()); - changeKey = change.getChangeKey().toString(); - assistantsWorkspace = projectName + PATH_ASSISTANTS; - } + @Inject + public PluginDataHandlerProvider( + @com.google.gerrit.extensions.annotations.PluginData Path defaultPluginDataPath, + GerritChange change) { + super(defaultPluginDataPath); + projectName = sanitizeFilename(change.getProjectName()); + changeKey = change.getChangeKey().toString(); + assistantsWorkspace = projectName + PATH_ASSISTANTS; + } - public PluginDataHandler getGlobalScope() { - return super.get(); - } + public PluginDataHandler getGlobalScope() { + return super.get(); + } - public PluginDataHandler getProjectScope() { - return super.get(projectName); - } + public PluginDataHandler getProjectScope() { + return super.get(projectName); + } - public PluginDataHandler getChangeScope() { - return super.get(changeKey); - } + public PluginDataHandler getChangeScope() { + return super.get(changeKey); + } - public PluginDataHandler getAssistantsWorkspace() { - return super.get(assistantsWorkspace); - } + public PluginDataHandler getAssistantsWorkspace() { + return super.get(assistantsWorkspace); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/listener/IEventHandlerType.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/listener/IEventHandlerType.java index dac591c..23a3502 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/listener/IEventHandlerType.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/listener/IEventHandlerType.java
@@ -1,10 +1,13 @@ package com.googlesource.gerrit.plugins.aicodereview.interfaces.listener; public interface IEventHandlerType { - enum PreprocessResult { - OK, EXIT, SWITCH_TO_PATCH_SET_CREATED - } + enum PreprocessResult { + OK, + EXIT, + SWITCH_TO_PATCH_SET_CREATED + } - PreprocessResult preprocessEvent(); - void processEvent() throws Exception; + PreprocessResult preprocessEvent(); + + void processEvent() throws Exception; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/gerrit/GerritClientPatchSet.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/gerrit/GerritClientPatchSet.java index d265c97..d543e62 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/gerrit/GerritClientPatchSet.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/gerrit/GerritClientPatchSet.java
@@ -3,15 +3,20 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; - import java.util.HashMap; public interface GerritClientPatchSet { - String getPatchSet(ChangeSetData changeSetData, GerritChange gerritChange) throws Exception; - boolean isDisabledUser(String authorUsername); - boolean isDisabledTopic(String topic); - void retrieveRevisionBase(GerritChange change); - Integer getNotNullAccountId(String authorUsername); - HashMap<String, FileDiffProcessed> getFileDiffsProcessed(); - Integer getRevisionBase(); + String getPatchSet(ChangeSetData changeSetData, GerritChange gerritChange) throws Exception; + + boolean isDisabledUser(String authorUsername); + + boolean isDisabledTopic(String topic); + + void retrieveRevisionBase(GerritChange change); + + Integer getNotNullAccountId(String authorUsername); + + HashMap<String, FileDiffProcessed> getFileDiffsProcessed(); + + Integer getRevisionBase(); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/openapi/ChatAIClient.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/openapi/ChatAIClient.java index e70b2d3..253d50f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/openapi/ChatAIClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/api/openapi/ChatAIClient.java
@@ -5,7 +5,8 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; public interface ChatAIClient { - AIChatResponseContent ask(ChangeSetData changeSetData, GerritChange change, String patchSet) - throws Exception; - String getRequestBody(); + AIChatResponseContent ask(ChangeSetData changeSetData, GerritChange change, String patchSet) + throws Exception; + + String getRequestBody(); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/prompt/ChatAIDataPrompt.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/prompt/ChatAIDataPrompt.java index 0869ad9..0b486b3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/prompt/ChatAIDataPrompt.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/common/client/prompt/ChatAIDataPrompt.java
@@ -1,12 +1,13 @@ package com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.prompt; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatMessageItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; - +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatMessageItem; import java.util.List; public interface ChatAIDataPrompt { - void addMessageItem(int i); - List<GerritComment> getCommentProperties(); - List<AIChatMessageItem> getMessageItems(); + void addMessageItem(int i); + + List<GerritComment> getCommentProperties(); + + List<AIChatMessageItem> getMessageItems(); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/stateful/client/prompt/ChatGptPromptStateful.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/stateful/client/prompt/ChatGptPromptStateful.java index 087a9e9..26162e9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/stateful/client/prompt/ChatGptPromptStateful.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/interfaces/mode/stateful/client/prompt/ChatGptPromptStateful.java
@@ -3,10 +3,15 @@ import java.util.List; public interface ChatGptPromptStateful { - void addGptAssistantInstructions(List<String> instructions); - String getDefaultGptAssistantDescription(); - String getDefaultGptAssistantInstructions(); - String getDefaultGptThreadReviewMessage(String patchSet); - String getAIRequestDataPrompt(); - void setCommentEvent(boolean isCommentEvent); + void addGptAssistantInstructions(List<String> instructions); + + String getDefaultGptAssistantDescription(); + + String getDefaultGptAssistantInstructions(); + + String getDefaultGptThreadReviewMessage(String patchSet); + + String getAIRequestDataPrompt(); + + void setCommentEvent(boolean isCommentEvent); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerExecutor.java index 2d0cf62..9abc5c9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerExecutor.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerExecutor.java
@@ -5,36 +5,34 @@ import com.google.gerrit.server.events.Event; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; -import com.google.inject.Singleton; import com.google.inject.Injector; +import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import lombok.extern.slf4j.Slf4j; - import java.util.concurrent.ScheduledExecutorService; +import lombok.extern.slf4j.Slf4j; @Slf4j @Singleton public class EventHandlerExecutor { - private final Injector injector; - private final ScheduledExecutorService executor; + private final Injector injector; + private final ScheduledExecutorService executor; - @Inject - EventHandlerExecutor( - Injector injector, - WorkQueue workQueue, - @PluginName String pluginName, - PluginConfigFactory pluginConfigFactory - ) { - this.injector = injector; - int maximumPoolSize = pluginConfigFactory.getFromGerritConfig(pluginName) - .getInt("maximumPoolSize", 2); - this.executor = workQueue.createQueue(maximumPoolSize, "ChatGPT request executor"); - } + @Inject + EventHandlerExecutor( + Injector injector, + WorkQueue workQueue, + @PluginName String pluginName, + PluginConfigFactory pluginConfigFactory) { + this.injector = injector; + int maximumPoolSize = + pluginConfigFactory.getFromGerritConfig(pluginName).getInt("maximumPoolSize", 2); + this.executor = workQueue.createQueue(maximumPoolSize, "ChatGPT request executor"); + } - public void execute(Configuration config, Event event) { - GerritEventContextModule contextModule = new GerritEventContextModule(config, event); - EventHandlerTask task = injector.createChildInjector(contextModule) - .getInstance(EventHandlerTask.class); - executor.execute(task); - } + public void execute(Configuration config, Event event) { + GerritEventContextModule contextModule = new GerritEventContextModule(config, event); + EventHandlerTask task = + injector.createChildInjector(contextModule).getInstance(EventHandlerTask.class); + executor.execute(task); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTask.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTask.java index 2e5931e..58f4042 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTask.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTask.java
@@ -15,154 +15,159 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git.GitRepoFiles; -import lombok.extern.slf4j.Slf4j; - import java.util.List; import java.util.Map; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; @Slf4j public class EventHandlerTask implements Runnable { - @VisibleForTesting - public enum Result { - OK, NOT_SUPPORTED, FAILURE - } - public enum SupportedEvents { - PATCH_SET_CREATED, - COMMENT_ADDED, - CHANGE_MERGED + @VisibleForTesting + public enum Result { + OK, + NOT_SUPPORTED, + FAILURE + } + + public enum SupportedEvents { + PATCH_SET_CREATED, + COMMENT_ADDED, + CHANGE_MERGED + } + + public static final Map<SupportedEvents, Class<?>> EVENT_CLASS_MAP = + Map.of( + SupportedEvents.PATCH_SET_CREATED, PatchSetCreatedEvent.class, + SupportedEvents.COMMENT_ADDED, CommentAddedEvent.class, + SupportedEvents.CHANGE_MERGED, ChangeMergedEvent.class); + + private static final Map<String, SupportedEvents> EVENT_TYPE_MAP = + Map.of( + "patchset-created", SupportedEvents.PATCH_SET_CREATED, + "comment-added", SupportedEvents.COMMENT_ADDED, + "change-merged", SupportedEvents.CHANGE_MERGED); + + private final Configuration config; + private final GerritClient gerritClient; + private final ChangeSetData changeSetData; + private final GerritChange change; + private final PatchSetReviewer reviewer; + private final GitRepoFiles gitRepoFiles; + private final PluginDataHandlerProvider pluginDataHandlerProvider; + + private SupportedEvents processing_event_type; + private IEventHandlerType eventHandlerType; + + @Inject + EventHandlerTask( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + PatchSetReviewer reviewer, + GerritClient gerritClient, + GitRepoFiles gitRepoFiles, + PluginDataHandlerProvider pluginDataHandlerProvider) { + this.changeSetData = changeSetData; + this.change = change; + this.reviewer = reviewer; + this.gerritClient = gerritClient; + this.config = config; + this.gitRepoFiles = gitRepoFiles; + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + } + + @Override + public void run() { + execute(); + } + + @VisibleForTesting + public Result execute() { + if (!preProcessEvent()) { + return Result.NOT_SUPPORTED; } - public static final Map<SupportedEvents, Class<?>> EVENT_CLASS_MAP = Map.of( - SupportedEvents.PATCH_SET_CREATED, PatchSetCreatedEvent.class, - SupportedEvents.COMMENT_ADDED, CommentAddedEvent.class, - SupportedEvents.CHANGE_MERGED, ChangeMergedEvent.class - ); + try { + log.info("Processing change: {}", change.getFullChangeId()); + eventHandlerType.processEvent(); + log.info("Finished processing change: {}", change.getFullChangeId()); + } catch (Exception e) { + log.error("Error while processing change: {}", change.getFullChangeId(), e); + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + return Result.FAILURE; + } + return Result.OK; + } - private static final Map<String, SupportedEvents> EVENT_TYPE_MAP = Map.of( - "patchset-created", SupportedEvents.PATCH_SET_CREATED, - "comment-added", SupportedEvents.COMMENT_ADDED, - "change-merged", SupportedEvents.CHANGE_MERGED - ); - - private final Configuration config; - private final GerritClient gerritClient; - private final ChangeSetData changeSetData; - private final GerritChange change; - private final PatchSetReviewer reviewer; - private final GitRepoFiles gitRepoFiles; - private final PluginDataHandlerProvider pluginDataHandlerProvider; - - private SupportedEvents processing_event_type; - private IEventHandlerType eventHandlerType; - - @Inject - EventHandlerTask( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - PatchSetReviewer reviewer, - GerritClient gerritClient, - GitRepoFiles gitRepoFiles, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - this.changeSetData = changeSetData; - this.change = change; - this.reviewer = reviewer; - this.gerritClient = gerritClient; - this.config = config; - this.gitRepoFiles = gitRepoFiles; - this.pluginDataHandlerProvider = pluginDataHandlerProvider; + private boolean preProcessEvent() { + String eventType = Optional.ofNullable(change.getEventType()).orElse(""); + log.info("Event type {}", eventType); + processing_event_type = EVENT_TYPE_MAP.get(eventType); + if (processing_event_type == null) { + return false; } - @Override - public void run() { - execute(); + if (!isReviewEnabled(change)) { + return false; } - @VisibleForTesting - public Result execute() { - if (!preProcessEvent()) { - return Result.NOT_SUPPORTED; + while (true) { + eventHandlerType = getEventHandlerType(); + switch (eventHandlerType.preprocessEvent()) { + case EXIT -> { + return false; } - - try { - log.info("Processing change: {}", change.getFullChangeId()); - eventHandlerType.processEvent(); - log.info("Finished processing change: {}", change.getFullChangeId()); - } catch (Exception e) { - log.error("Error while processing change: {}", change.getFullChangeId(), e); - if (e instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - return Result.FAILURE; + case SWITCH_TO_PATCH_SET_CREATED -> { + processing_event_type = SupportedEvents.PATCH_SET_CREATED; + continue; } - return Result.OK; + } + break; } - private boolean preProcessEvent() { - String eventType = Optional.ofNullable(change.getEventType()).orElse(""); - log.info("Event type {}", eventType); - processing_event_type = EVENT_TYPE_MAP.get(eventType); - if (processing_event_type == null) { - return false; - } + return true; + } - if (!isReviewEnabled(change)) { - return false; - } + private IEventHandlerType getEventHandlerType() { + return switch (processing_event_type) { + case PATCH_SET_CREATED -> + new EventHandlerTypePatchSetReview(config, changeSetData, change, reviewer, gerritClient); + case COMMENT_ADDED -> + new EventHandlerTypeCommentAdded(changeSetData, change, reviewer, gerritClient); + case CHANGE_MERGED -> + new EventHandlerTypeChangeMerged( + config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider); + }; + } - while (true) { - eventHandlerType = getEventHandlerType(); - switch (eventHandlerType.preprocessEvent()) { - case EXIT -> { - return false; - } - case SWITCH_TO_PATCH_SET_CREATED -> { - processing_event_type = SupportedEvents.PATCH_SET_CREATED; - continue; - } - } - break; - } - - return true; + private boolean isReviewEnabled(GerritChange change) { + List<String> enabledProjects = + Splitter.on(",").omitEmptyStrings().splitToList(config.getEnabledProjects()); + if (!config.isGlobalEnable() + && !enabledProjects.contains(change.getProjectNameKey().get()) + && !config.isProjectEnable()) { + log.debug("The project {} is not enabled for review", change.getProjectNameKey()); + return false; } - private IEventHandlerType getEventHandlerType() { - return switch (processing_event_type) { - case PATCH_SET_CREATED -> new EventHandlerTypePatchSetReview(config, changeSetData, change, reviewer, gerritClient); - case COMMENT_ADDED -> new EventHandlerTypeCommentAdded(changeSetData, change, reviewer, gerritClient); - case CHANGE_MERGED -> new EventHandlerTypeChangeMerged(config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider); - }; + String topic = getTopic(change).orElse(""); + log.debug("PatchSet Topic retrieved: '{}'", topic); + if (gerritClient.isDisabledTopic(topic)) { + log.info("Disabled review for PatchSets with Topic '{}'", topic); + return false; } - private boolean isReviewEnabled(GerritChange change) { - List<String> enabledProjects = Splitter.on(",").omitEmptyStrings() - .splitToList(config.getEnabledProjects()); - if (!config.isGlobalEnable() && - !enabledProjects.contains(change.getProjectNameKey().get()) && - !config.isProjectEnable()) { - log.debug("The project {} is not enabled for review", change.getProjectNameKey()); - return false; - } + return true; + } - String topic = getTopic(change).orElse(""); - log.debug("PatchSet Topic retrieved: '{}'", topic); - if (gerritClient.isDisabledTopic(topic)) { - log.info("Disabled review for PatchSets with Topic '{}'", topic); - return false; - } - - return true; + private Optional<String> getTopic(GerritChange change) { + try { + ChangeAttribute changeAttribute = change.getPatchSetEvent().change.get(); + return Optional.ofNullable(changeAttribute.topic); + } catch (NullPointerException e) { + return Optional.empty(); } - - private Optional<String> getTopic(GerritChange change) { - try { - ChangeAttribute changeAttribute = change.getPatchSetEvent().change.get(); - return Optional.ofNullable(changeAttribute.topic); - } catch (NullPointerException e) { - return Optional.empty(); - } - } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeChangeMerged.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeChangeMerged.java index 1e4af18..9e09af8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeChangeMerged.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeChangeMerged.java
@@ -11,43 +11,39 @@ @Slf4j public class EventHandlerTypeChangeMerged implements IEventHandlerType { - private final Configuration config; - private final ChangeSetData changeSetData; - private final GerritChange change; - private final GitRepoFiles gitRepoFiles; - private final PluginDataHandlerProvider pluginDataHandlerProvider; + private final Configuration config; + private final ChangeSetData changeSetData; + private final GerritChange change; + private final GitRepoFiles gitRepoFiles; + private final PluginDataHandlerProvider pluginDataHandlerProvider; - EventHandlerTypeChangeMerged( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - GitRepoFiles gitRepoFiles, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - this.config = config; - this.changeSetData = changeSetData; - this.change = change; - this.gitRepoFiles = gitRepoFiles; - this.pluginDataHandlerProvider = pluginDataHandlerProvider; - } + EventHandlerTypeChangeMerged( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + GitRepoFiles gitRepoFiles, + PluginDataHandlerProvider pluginDataHandlerProvider) { + this.config = config; + this.changeSetData = changeSetData; + this.change = change; + this.gitRepoFiles = gitRepoFiles; + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + } - @Override - public PreprocessResult preprocessEvent() { - return PreprocessResult.OK; - } + @Override + public PreprocessResult preprocessEvent() { + return PreprocessResult.OK; + } - @Override - public void processEvent() { - // TODO: Should we be firing assistant based stateful request items when the aiMode is stateless? - // This is extra paid for requests which aren't required if using GPT. - ChatGptAssistant chatGptAssistant = new ChatGptAssistant( - config, - changeSetData, - change, - gitRepoFiles, - pluginDataHandlerProvider - ); - chatGptAssistant.flushAssistantIds(); - chatGptAssistant.createVectorStore(); - } + @Override + public void processEvent() { + // TODO: Should we be firing assistant based stateful request items when the aiMode is + // stateless? + // This is extra paid for requests which aren't required if using GPT. + ChatGptAssistant chatGptAssistant = + new ChatGptAssistant( + config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider); + chatGptAssistant.flushAssistantIds(); + chatGptAssistant.createVectorStore(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeCommentAdded.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeCommentAdded.java index 6d25df4..5a243f0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeCommentAdded.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypeCommentAdded.java
@@ -9,41 +9,39 @@ @Slf4j public class EventHandlerTypeCommentAdded implements IEventHandlerType { - private final ChangeSetData changeSetData; - private final GerritChange change; - private final PatchSetReviewer reviewer; - private final GerritClient gerritClient; + private final ChangeSetData changeSetData; + private final GerritChange change; + private final PatchSetReviewer reviewer; + private final GerritClient gerritClient; - EventHandlerTypeCommentAdded( - ChangeSetData changeSetData, - GerritChange change, - PatchSetReviewer reviewer, - GerritClient gerritClient - ) { - this.changeSetData = changeSetData; - this.change = change; - this.reviewer = reviewer; - this.gerritClient = gerritClient; + EventHandlerTypeCommentAdded( + ChangeSetData changeSetData, + GerritChange change, + PatchSetReviewer reviewer, + GerritClient gerritClient) { + this.changeSetData = changeSetData; + this.change = change; + this.reviewer = reviewer; + this.gerritClient = gerritClient; + } + @Override + public PreprocessResult preprocessEvent() { + if (!gerritClient.retrieveLastComments(change)) { + if (changeSetData.getForcedReview()) { + return PreprocessResult.SWITCH_TO_PATCH_SET_CREATED; + } else { + log.info("No comments found for review"); + return PreprocessResult.EXIT; + } } + change.setIsCommentEvent(true); - @Override - public PreprocessResult preprocessEvent() { - if (!gerritClient.retrieveLastComments(change)) { - if (changeSetData.getForcedReview()) { - return PreprocessResult.SWITCH_TO_PATCH_SET_CREATED; - } else { - log.info("No comments found for review"); - return PreprocessResult.EXIT; - } - } - change.setIsCommentEvent(true); + return PreprocessResult.OK; + } - return PreprocessResult.OK; - } - - @Override - public void processEvent() throws Exception { - reviewer.review(change); - } + @Override + public void processEvent() throws Exception { + reviewer.review(change); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypePatchSetReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypePatchSetReview.java index d1ecbd4..eeda4f2 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypePatchSetReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/EventHandlerTypePatchSetReview.java
@@ -1,5 +1,7 @@ package com.googlesource.gerrit.plugins.aicodereview.listener; +import static com.google.gerrit.extensions.client.ChangeKind.REWORK; + import com.google.gerrit.extensions.client.ChangeKind; import com.google.gerrit.server.data.PatchSetAttribute; import com.googlesource.gerrit.plugins.aicodereview.PatchSetReviewer; @@ -8,77 +10,74 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - import java.util.Optional; - -import static com.google.gerrit.extensions.client.ChangeKind.REWORK; +import lombok.extern.slf4j.Slf4j; @Slf4j public class EventHandlerTypePatchSetReview implements IEventHandlerType { - private final Configuration config; - private final ChangeSetData changeSetData; - private final GerritChange change; - private final PatchSetReviewer reviewer; - private final GerritClient gerritClient; + private final Configuration config; + private final ChangeSetData changeSetData; + private final GerritChange change; + private final PatchSetReviewer reviewer; + private final GerritClient gerritClient; - EventHandlerTypePatchSetReview( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - PatchSetReviewer reviewer, - GerritClient gerritClient - ) { - this.config = config; - this.changeSetData = changeSetData; - this.change = change; - this.reviewer = reviewer; - this.gerritClient = gerritClient; + EventHandlerTypePatchSetReview( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + PatchSetReviewer reviewer, + GerritClient gerritClient) { + this.config = config; + this.changeSetData = changeSetData; + this.change = change; + this.reviewer = reviewer; + this.gerritClient = gerritClient; + } + + @Override + public PreprocessResult preprocessEvent() { + if (!isPatchSetReviewEnabled(change)) { + log.debug("Patch Set review disabled"); + return PreprocessResult.EXIT; } + gerritClient.retrievePatchSetInfo(change); - @Override - public PreprocessResult preprocessEvent() { - if (!isPatchSetReviewEnabled(change)) { - log.debug("Patch Set review disabled"); - return PreprocessResult.EXIT; - } - gerritClient.retrievePatchSetInfo(change); + return PreprocessResult.OK; + } - return PreprocessResult.OK; + @Override + public void processEvent() throws Exception { + reviewer.review(change); + } + + private boolean isPatchSetReviewEnabled(GerritChange change) { + if (!config.getAIReviewPatchSet()) { + log.debug("Disabled review function for created or updated PatchSets."); + return false; } - - @Override - public void processEvent() throws Exception { - reviewer.review(change); + Optional<PatchSetAttribute> patchSetAttributeOptional = change.getPatchSetAttribute(); + if (patchSetAttributeOptional.isEmpty()) { + log.info("PatchSetAttribute event properties not retrieved"); + return false; } - - private boolean isPatchSetReviewEnabled(GerritChange change) { - if (!config.getAIReviewPatchSet()) { - log.debug("Disabled review function for created or updated PatchSets."); - return false; - } - Optional<PatchSetAttribute> patchSetAttributeOptional = change.getPatchSetAttribute(); - if (patchSetAttributeOptional.isEmpty()) { - log.info("PatchSetAttribute event properties not retrieved"); - return false; - } - PatchSetAttribute patchSetAttribute = patchSetAttributeOptional.get(); - ChangeKind patchSetEventKind = patchSetAttribute.kind; - // The only Change kind that automatically triggers the review is REWORK. If review is forced via command, this - // condition is bypassed - if (patchSetEventKind != REWORK && !changeSetData.getForcedReview()) { - log.debug("Change kind '{}' not processed", patchSetEventKind); - return false; - } - String authorUsername = patchSetAttribute.author.username; - if (gerritClient.isDisabledUser(authorUsername)) { - log.info("Review of PatchSets from user '{}' is disabled.", authorUsername); - return false; - } - if (gerritClient.isWorkInProgress(change)) { - log.debug("Skipping Patch Set processing due to its WIP status."); - return false; - } - return true; + PatchSetAttribute patchSetAttribute = patchSetAttributeOptional.get(); + ChangeKind patchSetEventKind = patchSetAttribute.kind; + // The only Change kind that automatically triggers the review is REWORK. If review is forced + // via command, this + // condition is bypassed + if (patchSetEventKind != REWORK && !changeSetData.getForcedReview()) { + log.debug("Change kind '{}' not processed", patchSetEventKind); + return false; } + String authorUsername = patchSetAttribute.author.username; + if (gerritClient.isDisabledUser(authorUsername)) { + log.info("Review of PatchSets from user '{}' is disabled.", authorUsername); + return false; + } + if (gerritClient.isWorkInProgress(change)) { + log.debug("Skipping Patch Set processing due to its WIP status."); + return false; + } + return true; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritEventContextModule.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritEventContextModule.java index 423a09f..599a28d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritEventContextModule.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritEventContextModule.java
@@ -1,5 +1,7 @@ package com.googlesource.gerrit.plugins.aicodereview.listener; +import static com.google.inject.Scopes.SINGLETON; + import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.server.events.Event; import com.google.inject.Singleton; @@ -8,47 +10,45 @@ import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi.ChatAIClient; -import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.gerrit.GerritClientPatchSet; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientPatchSet; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatai.AIChatClientStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit.GerritClientPatchSetStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.chatai.AIChatClientStateless; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.gerrit.GerritClientPatchSetStateless; -import static com.google.inject.Scopes.SINGLETON; - public class GerritEventContextModule extends FactoryModule { - private final Event event; - private final Configuration config; + private final Event event; + private final Configuration config; - public GerritEventContextModule(Configuration config, Event event) { - this.event = event; - this.config = config; - } + public GerritEventContextModule(Configuration config, Event event) { + this.event = event; + this.config = config; + } - @Override - protected void configure() { - bind(ChatAIClient.class).to(getChatAIMode()); - bind(GerritClientPatchSet.class).to(getClientPatchSet()); + @Override + protected void configure() { + bind(ChatAIClient.class).to(getChatAIMode()); + bind(GerritClientPatchSet.class).to(getClientPatchSet()); - bind(Configuration.class).toInstance(config); - bind(GerritChange.class).toInstance(new GerritChange(event)); - bind(ChangeSetData.class).toProvider(ChangeSetDataProvider.class).in(SINGLETON); - bind(PluginDataHandler.class).toProvider(PluginDataHandlerProvider.class).in(Singleton.class); - } + bind(Configuration.class).toInstance(config); + bind(GerritChange.class).toInstance(new GerritChange(event)); + bind(ChangeSetData.class).toProvider(ChangeSetDataProvider.class).in(SINGLETON); + bind(PluginDataHandler.class).toProvider(PluginDataHandlerProvider.class).in(Singleton.class); + } - private Class<? extends ChatAIClient> getChatAIMode() { - return switch (config.getAIMode()){ - case stateful -> AIChatClientStateful.class; - case stateless -> AIChatClientStateless.class; - }; - } + private Class<? extends ChatAIClient> getChatAIMode() { + return switch (config.getAIMode()) { + case stateful -> AIChatClientStateful.class; + case stateless -> AIChatClientStateless.class; + }; + } - private Class<? extends GerritClientPatchSet> getClientPatchSet() { - return switch (config.getAIMode()){ - case stateful -> GerritClientPatchSetStateful.class; - case stateless -> GerritClientPatchSetStateless.class; - }; - } + private Class<? extends GerritClientPatchSet> getClientPatchSet() { + return switch (config.getAIMode()) { + case stateful -> GerritClientPatchSetStateful.class; + case stateless -> GerritClientPatchSetStateless.class; + }; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritListener.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritListener.java index 0bf3e21..8af562a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritListener.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/listener/GerritListener.java
@@ -1,58 +1,58 @@ package com.googlesource.gerrit.plugins.aicodereview.listener; +import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.EVENT_CLASS_MAP; + import com.google.gerrit.common.Nullable; import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Project; import com.google.gerrit.server.config.GerritInstanceId; -import com.google.gerrit.server.events.*; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.EventListener; +import com.google.gerrit.server.events.PatchSetEvent; import com.google.gerrit.server.project.NoSuchProjectException; import com.google.inject.Inject; import com.googlesource.gerrit.plugins.aicodereview.config.ConfigCreator; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import lombok.extern.slf4j.Slf4j; - import java.util.Objects; - -import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.EVENT_CLASS_MAP; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritListener implements EventListener { - private final String myInstanceId; - private final ConfigCreator configCreator; - private final EventHandlerExecutor evenHandlerExecutor; + private final String myInstanceId; + private final ConfigCreator configCreator; + private final EventHandlerExecutor evenHandlerExecutor; - @Inject - public GerritListener( - ConfigCreator configCreator, - EventHandlerExecutor evenHandlerExecutor, - @GerritInstanceId @Nullable String myInstanceId - ) { - this.configCreator = configCreator; - this.evenHandlerExecutor = evenHandlerExecutor; - this.myInstanceId = myInstanceId; + @Inject + public GerritListener( + ConfigCreator configCreator, + EventHandlerExecutor evenHandlerExecutor, + @GerritInstanceId @Nullable String myInstanceId) { + this.configCreator = configCreator; + this.evenHandlerExecutor = evenHandlerExecutor; + this.myInstanceId = myInstanceId; + } + + @Override + public void onEvent(Event event) { + if (!Objects.equals(event.instanceId, myInstanceId)) { + log.debug("Ignore event from another instance"); + return; + } + if (!EVENT_CLASS_MAP.containsValue(event.getClass())) { + log.debug("The event {} is not managed by the plugin", event); + return; } - @Override - public void onEvent(Event event) { - if (!Objects.equals(event.instanceId, myInstanceId)) { - log.debug("Ignore event from another instance"); - return; - } - if (!EVENT_CLASS_MAP.containsValue(event.getClass())) { - log.debug("The event {} is not managed by the plugin", event); - return; - } + log.info("Processing event: {}", event); + PatchSetEvent patchSetEvent = (PatchSetEvent) event; + Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey(); + Change.Key changeKey = patchSetEvent.getChangeKey(); - log.info("Processing event: {}", event); - PatchSetEvent patchSetEvent = (PatchSetEvent) event; - Project.NameKey projectNameKey = patchSetEvent.getProjectNameKey(); - Change.Key changeKey = patchSetEvent.getChangeKey(); - - try { - Configuration config = configCreator.createConfig(projectNameKey, changeKey); - evenHandlerExecutor.execute(config, patchSetEvent); - } catch (NoSuchProjectException e) { - log.error("Project not found: {}", projectNameKey, e); - } + try { + Configuration config = configCreator.createConfig(projectNameKey, changeKey); + evenHandlerExecutor.execute(config, patchSetEvent); + } catch (NoSuchProjectException e) { + log.error("Project not found: {}", projectNameKey, e); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/localization/Localizer.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/localization/Localizer.java index 6786836..e14b48f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/localization/Localizer.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/localization/Localizer.java
@@ -2,20 +2,20 @@ import com.google.inject.Inject; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import lombok.extern.slf4j.Slf4j; - import java.util.ResourceBundle; +import lombok.extern.slf4j.Slf4j; @Slf4j public class Localizer { - private final ResourceBundle resourceBundle; + private final ResourceBundle resourceBundle; - @Inject - public Localizer(Configuration config) { - this.resourceBundle = ResourceBundle.getBundle("localization.localTexts", config.getLocaleDefault()); - } + @Inject + public Localizer(Configuration config) { + this.resourceBundle = + ResourceBundle.getBundle("localization.localTexts", config.getLocaleDefault()); + } - public String getText(String key) { - return resourceBundle.getString(key); - } + public String getText(String key) { + return resourceBundle.getString(key); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/ClientBase.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/ClientBase.java index 479be40..1e1984e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/ClientBase.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/ClientBase.java
@@ -3,9 +3,9 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; public abstract class ClientBase { - protected Configuration config; + protected Configuration config; - public ClientBase(Configuration config) { - this.config = config; - } + public ClientBase(Configuration config) { + this.config = config; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritChange.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritChange.java index 803c069..c623f0d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritChange.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritChange.java
@@ -6,68 +6,70 @@ import com.google.gerrit.server.data.PatchSetAttribute; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.PatchSetEvent; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Optional; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - @Slf4j @Getter public class GerritChange { - private Event event; - private String eventType; - private long eventTimeStamp; - private PatchSetEvent patchSetEvent; - private Project.NameKey projectNameKey; - private BranchNameKey branchNameKey; - private Change.Key changeKey; - private String fullChangeId; - // "Boolean" is used instead of "boolean" to have "getIsCommentEvent" instead of "isCommentEvent" as getter method - // (due to Lombok's magic naming convention) - @Setter - private Boolean isCommentEvent = false; + private Event event; + private String eventType; + private long eventTimeStamp; + private PatchSetEvent patchSetEvent; + private Project.NameKey projectNameKey; + private BranchNameKey branchNameKey; + private Change.Key changeKey; + private String fullChangeId; + // "Boolean" is used instead of "boolean" to have "getIsCommentEvent" instead of "isCommentEvent" + // as getter method + // (due to Lombok's magic naming convention) + @Setter private Boolean isCommentEvent = false; - public GerritChange(Project.NameKey projectNameKey, BranchNameKey branchNameKey, Change.Key changeKey) { - this.projectNameKey = projectNameKey; - this.branchNameKey = branchNameKey; - this.changeKey = changeKey; - buildFullChangeId(); - } + public GerritChange( + Project.NameKey projectNameKey, BranchNameKey branchNameKey, Change.Key changeKey) { + this.projectNameKey = projectNameKey; + this.branchNameKey = branchNameKey; + this.changeKey = changeKey; + buildFullChangeId(); + } - public GerritChange(Event event) { - this( - ((PatchSetEvent) event).getProjectNameKey(), - ((PatchSetEvent) event).getBranchNameKey(), - ((PatchSetEvent) event).getChangeKey() - ); - this.event = event; - eventType = event.getType(); - eventTimeStamp = event.eventCreatedOn; - patchSetEvent = (PatchSetEvent) event; - } + public GerritChange(Event event) { + this( + ((PatchSetEvent) event).getProjectNameKey(), + ((PatchSetEvent) event).getBranchNameKey(), + ((PatchSetEvent) event).getChangeKey()); + this.event = event; + eventType = event.getType(); + eventTimeStamp = event.eventCreatedOn; + patchSetEvent = (PatchSetEvent) event; + } - public GerritChange(String fullChangeId) { - this.fullChangeId = fullChangeId; - } + public GerritChange(String fullChangeId) { + this.fullChangeId = fullChangeId; + } - public Optional<PatchSetAttribute> getPatchSetAttribute() { - try { - return Optional.ofNullable(patchSetEvent.patchSet.get()); - } - catch (NullPointerException e) { - return Optional.empty(); - } + public Optional<PatchSetAttribute> getPatchSetAttribute() { + try { + return Optional.ofNullable(patchSetEvent.patchSet.get()); + } catch (NullPointerException e) { + return Optional.empty(); } + } - public String getProjectName() { - return getProjectNameKey().toString(); - } + public String getProjectName() { + return getProjectNameKey().toString(); + } - private void buildFullChangeId() { - fullChangeId = String.join("~", URLEncoder.encode(projectNameKey.get(), StandardCharsets.UTF_8), - branchNameKey.shortName(), changeKey.get()); - } + private void buildFullChangeId() { + fullChangeId = + String.join( + "~", + URLEncoder.encode(projectNameKey.get(), StandardCharsets.UTF_8), + branchNameKey.shortName(), + changeKey.get()); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClient.java index b2fda14..250011a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClient.java
@@ -5,61 +5,60 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPermittedVotingRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.HashMap; +import lombok.extern.slf4j.Slf4j; @Slf4j @Singleton public class GerritClient { - private final GerritClientFacade gerritClientFacade; + private final GerritClientFacade gerritClientFacade; - @Inject - public GerritClient(GerritClientFacade gerritClientFacade) { - this.gerritClientFacade = gerritClientFacade; - } + @Inject + public GerritClient(GerritClientFacade gerritClientFacade) { + this.gerritClientFacade = gerritClientFacade; + } - public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { - return gerritClientFacade.getPermittedVotingRange(change); - } + public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { + return gerritClientFacade.getPermittedVotingRange(change); + } - public String getPatchSet(String fullChangeId) throws Exception { - return getPatchSet(new GerritChange(fullChangeId)); - } + public String getPatchSet(String fullChangeId) throws Exception { + return getPatchSet(new GerritChange(fullChangeId)); + } - public String getPatchSet(GerritChange change) throws Exception { - return gerritClientFacade.getPatchSet(change); - } + public String getPatchSet(GerritChange change) throws Exception { + return gerritClientFacade.getPatchSet(change); + } - public boolean isDisabledUser(String authorUsername) { - return gerritClientFacade.isDisabledUser(authorUsername); - } + public boolean isDisabledUser(String authorUsername) { + return gerritClientFacade.isDisabledUser(authorUsername); + } - public boolean isDisabledTopic(String topic) { - return gerritClientFacade.isDisabledTopic(topic); - } + public boolean isDisabledTopic(String topic) { + return gerritClientFacade.isDisabledTopic(topic); + } - public boolean isWorkInProgress(GerritChange change) { - return gerritClientFacade.isWorkInProgress(change); - } + public boolean isWorkInProgress(GerritChange change) { + return gerritClientFacade.isWorkInProgress(change); + } - public HashMap<String, FileDiffProcessed> getFileDiffsProcessed(GerritChange change) { - return gerritClientFacade.getFileDiffsProcessed(); - } + public HashMap<String, FileDiffProcessed> getFileDiffsProcessed(GerritChange change) { + return gerritClientFacade.getFileDiffsProcessed(); + } - public Integer getNotNullAccountId(String authorUsername) { - return gerritClientFacade.getNotNullAccountId(authorUsername); - } + public Integer getNotNullAccountId(String authorUsername) { + return gerritClientFacade.getNotNullAccountId(authorUsername); + } - public boolean retrieveLastComments(GerritChange change) { - return gerritClientFacade.retrieveLastComments(change); - } + public boolean retrieveLastComments(GerritChange change) { + return gerritClientFacade.retrieveLastComments(change); + } - public void retrievePatchSetInfo(GerritChange change) { - gerritClientFacade.retrievePatchSetInfo(change); - } + public void retrievePatchSetInfo(GerritChange change) { + gerritClientFacade.retrievePatchSetInfo(change); + } - public GerritClientData getClientData(GerritChange change) { - return gerritClientFacade.getClientData(change); - } + public GerritClientData getClientData(GerritChange change) { + return gerritClientFacade.getClientData(change); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientAccount.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientAccount.java index 25532c4..ea3da6e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientAccount.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientAccount.java
@@ -1,87 +1,87 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.extensions.common.GroupInfo; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.util.ManualRequestContext; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import lombok.extern.slf4j.Slf4j; - -import static java.util.stream.Collectors.toList; - import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritClientAccount extends GerritClientBase { - private final AccountCache accountCache; + private final AccountCache accountCache; - public GerritClientAccount(Configuration config, AccountCache accountCache) { - super(config); - this.accountCache = accountCache; - } + public GerritClientAccount(Configuration config, AccountCache accountCache) { + super(config); + this.accountCache = accountCache; + } - public boolean isDisabledUser(String authorUsername) { - List<String> enabledUsers = config.getEnabledUsers(); - List<String> disabledUsers = config.getDisabledUsers(); - return !enabledUsers.contains(Configuration.ENABLED_USERS_ALL) - && !enabledUsers.contains(authorUsername) - || disabledUsers.contains(authorUsername) - || isDisabledUserGroup(authorUsername); - } + public boolean isDisabledUser(String authorUsername) { + List<String> enabledUsers = config.getEnabledUsers(); + List<String> disabledUsers = config.getDisabledUsers(); + return !enabledUsers.contains(Configuration.ENABLED_USERS_ALL) + && !enabledUsers.contains(authorUsername) + || disabledUsers.contains(authorUsername) + || isDisabledUserGroup(authorUsername); + } - public boolean isDisabledTopic(String topic) { - List<String> enabledTopicFilter = config.getEnabledTopicFilter(); - List<String> disabledTopicFilter = config.getDisabledTopicFilter(); - return !enabledTopicFilter.contains(Configuration.ENABLED_TOPICS_ALL) - && enabledTopicFilter.stream().noneMatch(topic::contains) - || !topic.isEmpty() && disabledTopicFilter.stream().anyMatch(topic::contains); - } + public boolean isDisabledTopic(String topic) { + List<String> enabledTopicFilter = config.getEnabledTopicFilter(); + List<String> disabledTopicFilter = config.getDisabledTopicFilter(); + return !enabledTopicFilter.contains(Configuration.ENABLED_TOPICS_ALL) + && enabledTopicFilter.stream().noneMatch(topic::contains) + || !topic.isEmpty() && disabledTopicFilter.stream().anyMatch(topic::contains); + } - protected Optional<Integer> getAccountId(String authorUsername) { - try { - return accountCache - .getByUsername(authorUsername) - .map(accountState -> accountState.account().id().get()); - } - catch (Exception e) { - log.error("Could not find account ID for username '{}'", authorUsername); - return Optional.empty(); - } + protected Optional<Integer> getAccountId(String authorUsername) { + try { + return accountCache + .getByUsername(authorUsername) + .map(accountState -> accountState.account().id().get()); + } catch (Exception e) { + log.error("Could not find account ID for username '{}'", authorUsername); + return Optional.empty(); } + } - public Integer getNotNullAccountId(String authorUsername) { - return getAccountId(authorUsername).orElseThrow(() -> new NoSuchElementException( - String.format("Error retrieving '%s' account ID in Gerrit", authorUsername))); - } + public Integer getNotNullAccountId(String authorUsername) { + return getAccountId(authorUsername) + .orElseThrow( + () -> + new NoSuchElementException( + String.format("Error retrieving '%s' account ID in Gerrit", authorUsername))); + } - private List<String> getAccountGroups(Integer accountId) { - try (ManualRequestContext requestContext = config.openRequestContext()) { - List<GroupInfo> groups = config.getGerritApi().accounts().id(accountId).getGroups(); - return groups.stream().map(g -> g.name).collect(toList()); - } - catch (Exception e) { - log.error("Could not find groups for account ID {}", accountId); - return null; - } + private List<String> getAccountGroups(Integer accountId) { + try (ManualRequestContext requestContext = config.openRequestContext()) { + List<GroupInfo> groups = config.getGerritApi().accounts().id(accountId).getGroups(); + return groups.stream().map(g -> g.name).collect(toList()); + } catch (Exception e) { + log.error("Could not find groups for account ID {}", accountId); + return null; } + } - private boolean isDisabledUserGroup(String authorUsername) { - List<String> enabledGroups = config.getEnabledGroups(); - List<String> disabledGroups = config.getDisabledGroups(); - if (enabledGroups.isEmpty() && disabledGroups.isEmpty()) { - return false; - } - Optional<Integer> accountId = getAccountId(authorUsername); - if (accountId.isEmpty()) { - return false; - } - List<String> accountGroups = getAccountGroups(accountId.orElse(-1)); - if (accountGroups == null || accountGroups.isEmpty()) { - return false; - } - return !enabledGroups.contains(Configuration.ENABLED_GROUPS_ALL) - && enabledGroups.stream().noneMatch(accountGroups::contains) - || disabledGroups.stream().anyMatch(accountGroups::contains); + private boolean isDisabledUserGroup(String authorUsername) { + List<String> enabledGroups = config.getEnabledGroups(); + List<String> disabledGroups = config.getDisabledGroups(); + if (enabledGroups.isEmpty() && disabledGroups.isEmpty()) { + return false; } + Optional<Integer> accountId = getAccountId(authorUsername); + if (accountId.isEmpty()) { + return false; + } + List<String> accountGroups = getAccountGroups(accountId.orElse(-1)); + if (accountGroups == null || accountGroups.isEmpty()) { + return false; + } + return !enabledGroups.contains(Configuration.ENABLED_GROUPS_ALL) + && enabledGroups.stream().noneMatch(accountGroups::contains) + || disabledGroups.stream().anyMatch(accountGroups::contains); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientBase.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientBase.java index 0c5ed8e..5d6d0a3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientBase.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientBase.java
@@ -3,18 +3,15 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; +import java.util.HashMap; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.HashMap; - - @Slf4j public abstract class GerritClientBase extends ClientBase { - @Getter - protected HashMap<String, FileDiffProcessed> fileDiffsProcessed = new HashMap<>(); + @Getter protected HashMap<String, FileDiffProcessed> fileDiffsProcessed = new HashMap<>(); - public GerritClientBase(Configuration config) { - super(config); - } + public GerritClientBase(Configuration config) { + super(config); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientComments.java index 3c65805..cfe8c1d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientComments.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientComments.java
@@ -1,5 +1,11 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit; +import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientDetail.toAuthor; +import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientDetail.toDateString; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TimeUtils.getTimeStamp; +import static java.util.stream.Collectors.toList; + import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.server.account.AccountCache; @@ -14,189 +20,184 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.CommentData; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import static java.util.stream.Collectors.toList; - -import java.util.*; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TimeUtils.getTimeStamp; -import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientDetail.toAuthor; -import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientDetail.toDateString; -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; - @Slf4j public class GerritClientComments extends GerritClientAccount { - private static final Integer MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT = 2; + private static final Integer MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT = 2; - private final ChangeSetData changeSetData; - private final HashMap<String, GerritComment> commentMap; - private final HashMap<String, GerritComment> patchSetCommentMap; - private final PluginDataHandlerProvider pluginDataHandlerProvider; - private final Localizer localizer; + private final ChangeSetData changeSetData; + private final HashMap<String, GerritComment> commentMap; + private final HashMap<String, GerritComment> patchSetCommentMap; + private final PluginDataHandlerProvider pluginDataHandlerProvider; + private final Localizer localizer; - private String authorUsername; - @Getter - private List<GerritComment> commentProperties; + private String authorUsername; + @Getter private List<GerritComment> commentProperties; - @VisibleForTesting - @Inject - public GerritClientComments( - Configuration config, - AccountCache accountCache, - ChangeSetData changeSetData, - PluginDataHandlerProvider pluginDataHandlerProvider, - Localizer localizer - ) { - super(config, accountCache); - this.changeSetData = changeSetData; - this.pluginDataHandlerProvider = pluginDataHandlerProvider; - this.localizer = localizer; - commentProperties = new ArrayList<>(); - commentMap = new HashMap<>(); - patchSetCommentMap = new HashMap<>(); + @VisibleForTesting + @Inject + public GerritClientComments( + Configuration config, + AccountCache accountCache, + ChangeSetData changeSetData, + PluginDataHandlerProvider pluginDataHandlerProvider, + Localizer localizer) { + super(config, accountCache); + this.changeSetData = changeSetData; + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + this.localizer = localizer; + commentProperties = new ArrayList<>(); + commentMap = new HashMap<>(); + patchSetCommentMap = new HashMap<>(); + } + + public CommentData getCommentData() { + return new CommentData(commentProperties, commentMap, patchSetCommentMap); + } + + public boolean retrieveLastComments(GerritChange change) { + CommentAddedEvent commentAddedEvent = (CommentAddedEvent) change.getEvent(); + authorUsername = commentAddedEvent.author.get().username; + log.debug("Found comments by '{}' on {}", authorUsername, change.getEventTimeStamp()); + if (authorUsername.equals(config.getGerritUserName())) { + log.debug("These are the Bot's own comments, do not process them."); + return false; } - - public CommentData getCommentData() { - return new CommentData(commentProperties, commentMap, patchSetCommentMap); + if (isDisabledUser(authorUsername)) { + log.info("Review of comments from user '{}' is disabled.", authorUsername); + return false; } + addLastComments(change); - public boolean retrieveLastComments(GerritChange change) { - CommentAddedEvent commentAddedEvent = (CommentAddedEvent) change.getEvent(); - authorUsername = commentAddedEvent.author.get().username; - log.debug("Found comments by '{}' on {}", authorUsername, change.getEventTimeStamp()); - if (authorUsername.equals(config.getGerritUserName())) { - log.debug("These are the Bot's own comments, do not process them."); - return false; + return !commentProperties.isEmpty(); + } + + public void retrieveAllComments(GerritChange change) { + try { + retrieveComments(change); + } catch (Exception e) { + log.error("Error while retrieving all comments for change: {}", change.getFullChangeId(), e); + } + } + + private List<GerritComment> retrieveComments(GerritChange change) throws Exception { + try (ManualRequestContext requestContext = config.openRequestContext()) { + Map<String, List<CommentInfo>> comments = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .commentsRequest() + .get(); + + // note that list of Map.Entry was used in order to keep the original response order + List<Map.Entry<String, List<GerritComment>>> lastCommentEntries = + comments.entrySet().stream() + .map( + entry -> + Map.entry( + entry.getKey(), + entry.getValue().stream() + .map(GerritClientComments::toComment) + .collect(toList()))) + .collect(toList()); + + String latestChangeMessageId = null; + HashMap<String, List<GerritComment>> latestComments = new HashMap<>(); + for (Map.Entry<String, List<GerritComment>> entry : lastCommentEntries) { + String filename = entry.getKey(); + log.info("Commented filename: {}", filename); + + List<GerritComment> commentsArray = entry.getValue(); + + for (GerritComment commentObject : commentsArray) { + commentObject.setFilename(filename); + String commentId = commentObject.getId(); + String changeMessageId = commentObject.getChangeMessageId(); + String commentAuthorUsername = commentObject.getAuthor().getUsername(); + log.debug( + "Change Message Id: {} - Author: {}", latestChangeMessageId, commentAuthorUsername); + long updatedTimeStamp = getTimeStamp(commentObject.getUpdated()); + if (commentAuthorUsername.equals(authorUsername) + && updatedTimeStamp + >= change.getEventTimeStamp() - MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT) { + log.debug("Found comment with updatedTimeStamp : {}", updatedTimeStamp); + latestChangeMessageId = changeMessageId; + } + latestComments + .computeIfAbsent(changeMessageId, k -> new ArrayList<>()) + .add(commentObject); + commentMap.put(commentId, commentObject); + if (filename.equals(GERRIT_PATCH_SET_FILENAME)) { + patchSetCommentMap.put(changeMessageId, commentObject); + } } - if (isDisabledUser(authorUsername)) { - log.info("Review of comments from user '{}' is disabled.", authorUsername); - return false; - } - addLastComments(change); + } - return !commentProperties.isEmpty(); + return latestComments.getOrDefault(latestChangeMessageId, null); } + } - public void retrieveAllComments(GerritChange change) { - try { - retrieveComments(change); - } catch (Exception e) { - log.error("Error while retrieving all comments for change: {}", change.getFullChangeId(), e); - } - } - - private List<GerritComment> retrieveComments(GerritChange change) throws Exception { - try (ManualRequestContext requestContext = config.openRequestContext()) { - Map<String, List<CommentInfo>> comments = - config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .commentsRequest() - .get(); - - // note that list of Map.Entry was used in order to keep the original response order - List<Map.Entry<String, List<GerritComment>>> lastCommentEntries = - comments.entrySet().stream() - .map( - entry -> - Map.entry( - entry.getKey(), - entry.getValue().stream() - .map(GerritClientComments::toComment) - .collect(toList()))) - .collect(toList()); - - String latestChangeMessageId = null; - HashMap<String, List<GerritComment>> latestComments = new HashMap<>(); - for (Map.Entry<String, List<GerritComment>> entry : lastCommentEntries) { - String filename = entry.getKey(); - log.info("Commented filename: {}", filename); - - List<GerritComment> commentsArray = entry.getValue(); - - for (GerritComment commentObject : commentsArray) { - commentObject.setFilename(filename); - String commentId = commentObject.getId(); - String changeMessageId = commentObject.getChangeMessageId(); - String commentAuthorUsername = commentObject.getAuthor().getUsername(); - log.debug( - "Change Message Id: {} - Author: {}", latestChangeMessageId, commentAuthorUsername); - long updatedTimeStamp = getTimeStamp(commentObject.getUpdated()); - if (commentAuthorUsername.equals(authorUsername) - && updatedTimeStamp - >= change.getEventTimeStamp() - MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT) { - log.debug("Found comment with updatedTimeStamp : {}", updatedTimeStamp); - latestChangeMessageId = changeMessageId; - } - latestComments - .computeIfAbsent(changeMessageId, k -> new ArrayList<>()) - .add(commentObject); - commentMap.put(commentId, commentObject); - if (filename.equals(GERRIT_PATCH_SET_FILENAME)) { - patchSetCommentMap.put(changeMessageId, commentObject); - } - } + private void addLastComments(GerritChange change) { + ClientMessage clientMessage = + new ClientMessage(config, changeSetData, pluginDataHandlerProvider, localizer); + try { + List<GerritComment> latestComments = retrieveComments(change); + if (latestComments == null) { + return; + } + for (GerritComment latestComment : latestComments) { + String commentMessage = latestComment.getMessage(); + if (clientMessage.isBotAddressed(commentMessage)) { + if (clientMessage.parseCommands(commentMessage, true)) { + if (clientMessage.isContainingHistoryCommand()) { + clientMessage.processHistoryCommand(); } - - return latestComments.getOrDefault(latestChangeMessageId, null); + commentProperties.clear(); + return; + } + commentProperties.add(latestComment); } + } + } catch (Exception e) { + log.error("Error while retrieving last comments for change: {}", change.getFullChangeId(), e); } + } - private void addLastComments(GerritChange change) { - ClientMessage clientMessage = new ClientMessage(config, changeSetData, pluginDataHandlerProvider, localizer); - try { - List<GerritComment> latestComments = retrieveComments(change); - if (latestComments == null) { - return; - } - for (GerritComment latestComment : latestComments) { - String commentMessage = latestComment.getMessage(); - if (clientMessage.isBotAddressed(commentMessage)) { - if (clientMessage.parseCommands(commentMessage, true)) { - if (clientMessage.isContainingHistoryCommand()) { - clientMessage.processHistoryCommand(); - } - commentProperties.clear(); - return; - } - commentProperties.add(latestComment); - } - } - } catch (Exception e) { - log.error("Error while retrieving last comments for change: {}", change.getFullChangeId(), e); - } - } - - private static GerritComment toComment(CommentInfo comment) { - GerritComment gerritComment = new GerritComment(); - gerritComment.setAuthor(toAuthor(comment.author)); - gerritComment.setChangeMessageId(comment.changeMessageId); - gerritComment.setUnresolved(comment.unresolved); - gerritComment.setPatchSet(comment.patchSet); - gerritComment.setId(comment.id); - gerritComment.setTag(comment.tag); - gerritComment.setLine(comment.line); - Optional.ofNullable(comment.range) - .ifPresent( - range -> - gerritComment.setRange( - GerritCodeRange.builder() - .startLine(range.startLine) - .endLine(range.endLine) - .startCharacter(range.startCharacter) - .endCharacter(range.endCharacter) - .build())); - gerritComment.setInReplyTo(comment.inReplyTo); - Optional.ofNullable(comment.updated) - .ifPresent(updated -> gerritComment.setUpdated(toDateString(updated))); - gerritComment.setMessage(comment.message); - gerritComment.setCommitId(comment.commitId); - return gerritComment; - } + private static GerritComment toComment(CommentInfo comment) { + GerritComment gerritComment = new GerritComment(); + gerritComment.setAuthor(toAuthor(comment.author)); + gerritComment.setChangeMessageId(comment.changeMessageId); + gerritComment.setUnresolved(comment.unresolved); + gerritComment.setPatchSet(comment.patchSet); + gerritComment.setId(comment.id); + gerritComment.setTag(comment.tag); + gerritComment.setLine(comment.line); + Optional.ofNullable(comment.range) + .ifPresent( + range -> + gerritComment.setRange( + GerritCodeRange.builder() + .startLine(range.startLine) + .endLine(range.endLine) + .startCharacter(range.startCharacter) + .endCharacter(range.endCharacter) + .build())); + gerritComment.setInReplyTo(comment.inReplyTo); + Optional.ofNullable(comment.updated) + .ifPresent(updated -> gerritComment.setUpdated(toDateString(updated))); + gerritComment.setMessage(comment.message); + gerritComment.setCommitId(comment.commitId); + return gerritComment; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientDetail.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientDetail.java index 7d2630a..de9a826 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientDetail.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientDetail.java
@@ -1,5 +1,8 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; + import com.google.gerrit.entities.LabelId; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ApprovalInfo; @@ -12,11 +15,6 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPatchSetDetail; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPermittedVotingRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; - import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.List; @@ -25,147 +23,152 @@ import java.util.Optional; import java.util.Set; import java.util.TimeZone; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritClientDetail { - private static final SimpleDateFormat DATE_FORMAT = newFormat(); + private static final SimpleDateFormat DATE_FORMAT = newFormat(); - private GerritPatchSetDetail gerritPatchSetDetail; - private final int gptAccountId; - private final Configuration config; + private GerritPatchSetDetail gerritPatchSetDetail; + private final int gptAccountId; + private final Configuration config; - public GerritClientDetail(Configuration config, ChangeSetData changeSetData) { - this.gptAccountId = changeSetData.getGptAccountId(); - this.config = config; + public GerritClientDetail(Configuration config, ChangeSetData changeSetData) { + this.gptAccountId = changeSetData.getGptAccountId(); + this.config = config; + } + + public List<GerritComment> getMessages(GerritChange change) { + loadPatchSetDetail(change); + return gerritPatchSetDetail.getMessages(); + } + + public boolean isWorkInProgress(GerritChange change) { + loadPatchSetDetail(change); + return gerritPatchSetDetail.getWorkInProgress() != null + && gerritPatchSetDetail.getWorkInProgress(); + } + + public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { + loadPatchSetDetail(change); + List<GerritPatchSetDetail.Permission> permissions = + gerritPatchSetDetail.getLabels().getCodeReview().getAll(); + if (permissions == null) { + log.debug("No limitations on the AICodeReview voting range were detected"); + return null; } - - public List<GerritComment> getMessages(GerritChange change) { - loadPatchSetDetail(change); - return gerritPatchSetDetail.getMessages(); + for (GerritPatchSetDetail.Permission permission : permissions) { + if (permission.getAccountId() == gptAccountId) { + log.debug( + "PatchSet voting range detected for AICodeReview user: {}", + permission.getPermittedVotingRange()); + return permission.getPermittedVotingRange(); + } } + return null; + } - public boolean isWorkInProgress(GerritChange change) { - loadPatchSetDetail(change); - return gerritPatchSetDetail.getWorkInProgress() != null && gerritPatchSetDetail.getWorkInProgress(); + private void loadPatchSetDetail(GerritChange change) { + if (gerritPatchSetDetail != null) { + return; } - - public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { - loadPatchSetDetail(change); - List<GerritPatchSetDetail.Permission> permissions = gerritPatchSetDetail.getLabels().getCodeReview().getAll(); - if (permissions == null) { - log.debug("No limitations on the AICodeReview voting range were detected"); - return null; - } - for (GerritPatchSetDetail.Permission permission : permissions) { - if (permission.getAccountId() == gptAccountId) { - log.debug("PatchSet voting range detected for AICodeReview user: {}", permission.getPermittedVotingRange()); - return permission.getPermittedVotingRange(); - } - } - return null; + try { + gerritPatchSetDetail = getReviewDetail(change); + } catch (Exception e) { + log.error("Error retrieving PatchSet details", e); } + } - private void loadPatchSetDetail(GerritChange change) { - if (gerritPatchSetDetail != null) { - return; - } - try { - gerritPatchSetDetail = getReviewDetail(change); - } - catch (Exception e) { - log.error("Error retrieving PatchSet details", e); - } + private GerritPatchSetDetail getReviewDetail(GerritChange change) throws Exception { + try (ManualRequestContext requestContext = config.openRequestContext()) { + ChangeInfo info = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .get(); + + GerritPatchSetDetail detail = new GerritPatchSetDetail(); + detail.setWorkInProgress(info.workInProgress); + Optional.ofNullable(info.labels) + .map(Map::entrySet) + .map(Set::stream) + .flatMap( + labels -> + labels + .filter(label -> LabelId.CODE_REVIEW.equals(label.getKey())) + .map(GerritClientDetail::toLabels) + .findAny()) + .ifPresent(detail::setLabels); + Optional.ofNullable(info.messages) + .map(messages -> messages.stream().map(GerritClientDetail::toComment).collect(toList())) + .ifPresent(detail::setMessages); + + return detail; } + } - private GerritPatchSetDetail getReviewDetail(GerritChange change) throws Exception { - try (ManualRequestContext requestContext = config.openRequestContext()) { - ChangeInfo info = - config - .getGerritApi() - .changes() - .id(change.getProjectName(), change.getBranchNameKey().shortName(), change.getChangeKey().get()) - .get(); + private static GerritPatchSetDetail.Labels toLabels(Entry<String, LabelInfo> label) { + List<GerritPatchSetDetail.Permission> permissions = + Optional.ofNullable(label.getValue().all) + .map(all -> all.stream().map(GerritClientDetail::toPermission).collect(toList())) + .orElse(emptyList()); + GerritPatchSetDetail.CodeReview codeReview = new GerritPatchSetDetail.CodeReview(); + codeReview.setAll(permissions); + GerritPatchSetDetail.Labels labels = new GerritPatchSetDetail.Labels(); + labels.setCodeReview(codeReview); + return labels; + } - GerritPatchSetDetail detail = new GerritPatchSetDetail(); - detail.setWorkInProgress(info.workInProgress); - Optional.ofNullable(info.labels) - .map(Map::entrySet) - .map(Set::stream) - .flatMap( - labels -> - labels - .filter(label -> LabelId.CODE_REVIEW.equals(label.getKey())) - .map(GerritClientDetail::toLabels) - .findAny()) - .ifPresent(detail::setLabels); - Optional.ofNullable(info.messages) - .map(messages -> messages.stream().map(GerritClientDetail::toComment).collect(toList())) - .ifPresent(detail::setMessages); + private static GerritPatchSetDetail.Permission toPermission(ApprovalInfo value) { + GerritPatchSetDetail.Permission permission = new GerritPatchSetDetail.Permission(); + permission.setValue(value.value); + Optional.ofNullable(value.date).ifPresent(date -> permission.setDate(toDateString(date))); + Optional.ofNullable(value.permittedVotingRange) + .ifPresent( + permittedVotingRange -> { + GerritPermittedVotingRange range = new GerritPermittedVotingRange(); + range.setMin(permittedVotingRange.min); + range.setMax(permittedVotingRange.max); + permission.setPermittedVotingRange(range); + }); + permission.setAccountId(value._accountId); + return permission; + } - return detail; - } - } + private static GerritComment toComment(ChangeMessageInfo message) { + GerritComment comment = new GerritComment(); + Optional.ofNullable(message.author).ifPresent(author -> comment.setAuthor(toAuthor(author))); + comment.setId(message.id); + comment.setTag(message.tag); + Optional.ofNullable(message.date).ifPresent(date -> comment.setDate(toDateString(date))); + comment.setMessage(message.message); + comment.setPatchSet(message._revisionNumber); + return comment; + } - private static GerritPatchSetDetail.Labels toLabels(Entry<String, LabelInfo> label) { - List<GerritPatchSetDetail.Permission> permissions = - Optional.ofNullable(label.getValue().all) - .map(all -> all.stream().map(GerritClientDetail::toPermission).collect(toList())) - .orElse(emptyList()); - GerritPatchSetDetail.CodeReview codeReview = new GerritPatchSetDetail.CodeReview(); - codeReview.setAll(permissions); - GerritPatchSetDetail.Labels labels = new GerritPatchSetDetail.Labels(); - labels.setCodeReview(codeReview); - return labels; - } + static GerritComment.Author toAuthor(AccountInfo authorInfo) { + GerritComment.Author author = new GerritComment.Author(); + author.setAccountId(authorInfo._accountId); + author.setName(authorInfo.name); + author.setDisplayName(author.getDisplayName()); + author.setEmail(authorInfo.email); + author.setUsername(authorInfo.username); + return author; + } - private static GerritPatchSetDetail.Permission toPermission(ApprovalInfo value) { - GerritPatchSetDetail.Permission permission = new GerritPatchSetDetail.Permission(); - permission.setValue(value.value); - Optional.ofNullable(value.date).ifPresent(date -> permission.setDate(toDateString(date))); - Optional.ofNullable(value.permittedVotingRange) - .ifPresent( - permittedVotingRange -> { - GerritPermittedVotingRange range = new GerritPermittedVotingRange(); - range.setMin(permittedVotingRange.min); - range.setMax(permittedVotingRange.max); - permission.setPermittedVotingRange(range); - }); - permission.setAccountId(value._accountId); - return permission; - } + /** Date format copied from <b>com.google.gerrit.json.SqlTimestampDeserializer</b> */ + static String toDateString(Timestamp input) { + return DATE_FORMAT.format(input) + "000000"; + } - private static GerritComment toComment(ChangeMessageInfo message) { - GerritComment comment = new GerritComment(); - Optional.ofNullable(message.author).ifPresent(author -> comment.setAuthor(toAuthor(author))); - comment.setId(message.id); - comment.setTag(message.tag); - Optional.ofNullable(message.date).ifPresent(date -> comment.setDate(toDateString(date))); - comment.setMessage(message.message); - comment.setPatchSet(message._revisionNumber); - return comment; - } - - static GerritComment.Author toAuthor(AccountInfo authorInfo ) { - GerritComment.Author author = new GerritComment.Author(); - author.setAccountId(authorInfo._accountId); - author.setName(authorInfo.name); - author.setDisplayName(author.getDisplayName()); - author.setEmail(authorInfo.email); - author.setUsername(authorInfo.username); - return author; - } - - /** - * Date format copied from <b>com.google.gerrit.json.SqlTimestampDeserializer</b> - */ - static String toDateString(Timestamp input) { - return DATE_FORMAT.format(input) + "000000"; - } - - private static SimpleDateFormat newFormat() { - SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - f.setTimeZone(TimeZone.getTimeZone("UTC")); - f.setLenient(true); - return f; - } + private static SimpleDateFormat newFormat() { + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + f.setTimeZone(TimeZone.getTimeZone("UTC")); + f.setLenient(true); + return f; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientFacade.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientFacade.java index 0278794..c136c34 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientFacade.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientFacade.java
@@ -8,73 +8,71 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPermittedVotingRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.HashMap; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritClientFacade { - private final ChangeSetData changeSetData; - private final GerritClientDetail gerritClientDetail; - private final GerritClientComments gerritClientComments; - private final GerritClientPatchSet gerritClientPatchSet; + private final ChangeSetData changeSetData; + private final GerritClientDetail gerritClientDetail; + private final GerritClientComments gerritClientComments; + private final GerritClientPatchSet gerritClientPatchSet; - @VisibleForTesting - @Inject - public GerritClientFacade( - Configuration config, - ChangeSetData changeSetData, - GerritClientComments gerritClientComments, - GerritClientPatchSet gerritClientPatchSet) { - gerritClientDetail = new GerritClientDetail(config, changeSetData); - this.gerritClientPatchSet = gerritClientPatchSet; - this.changeSetData = changeSetData; - this.gerritClientComments = gerritClientComments; - } + @VisibleForTesting + @Inject + public GerritClientFacade( + Configuration config, + ChangeSetData changeSetData, + GerritClientComments gerritClientComments, + GerritClientPatchSet gerritClientPatchSet) { + gerritClientDetail = new GerritClientDetail(config, changeSetData); + this.gerritClientPatchSet = gerritClientPatchSet; + this.changeSetData = changeSetData; + this.gerritClientComments = gerritClientComments; + } - public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { - return gerritClientDetail.getPermittedVotingRange(change); - } + public GerritPermittedVotingRange getPermittedVotingRange(GerritChange change) { + return gerritClientDetail.getPermittedVotingRange(change); + } - public String getPatchSet(GerritChange change) throws Exception { - return gerritClientPatchSet.getPatchSet(changeSetData, change); - } + public String getPatchSet(GerritChange change) throws Exception { + return gerritClientPatchSet.getPatchSet(changeSetData, change); + } - public boolean isDisabledUser(String authorUsername) { - return gerritClientPatchSet.isDisabledUser(authorUsername); - } + public boolean isDisabledUser(String authorUsername) { + return gerritClientPatchSet.isDisabledUser(authorUsername); + } - public boolean isDisabledTopic(String topic) { - return gerritClientPatchSet.isDisabledTopic(topic); - } + public boolean isDisabledTopic(String topic) { + return gerritClientPatchSet.isDisabledTopic(topic); + } - public boolean isWorkInProgress(GerritChange change) { - return gerritClientDetail.isWorkInProgress(change); - } + public boolean isWorkInProgress(GerritChange change) { + return gerritClientDetail.isWorkInProgress(change); + } - public HashMap<String, FileDiffProcessed> getFileDiffsProcessed() { - return gerritClientPatchSet.getFileDiffsProcessed(); - } + public HashMap<String, FileDiffProcessed> getFileDiffsProcessed() { + return gerritClientPatchSet.getFileDiffsProcessed(); + } - public Integer getNotNullAccountId(String authorUsername) { - return gerritClientPatchSet.getNotNullAccountId(authorUsername); - } + public Integer getNotNullAccountId(String authorUsername) { + return gerritClientPatchSet.getNotNullAccountId(authorUsername); + } - public boolean retrieveLastComments(GerritChange change) { - return gerritClientComments.retrieveLastComments(change); - } + public boolean retrieveLastComments(GerritChange change) { + return gerritClientComments.retrieveLastComments(change); + } - public void retrievePatchSetInfo(GerritChange change) { - gerritClientComments.retrieveAllComments(change); - gerritClientPatchSet.retrieveRevisionBase(change); - } + public void retrievePatchSetInfo(GerritChange change) { + gerritClientComments.retrieveAllComments(change); + gerritClientPatchSet.retrieveRevisionBase(change); + } - public GerritClientData getClientData(GerritChange change) { - return new GerritClientData( - gerritClientPatchSet.getFileDiffsProcessed(), - gerritClientDetail.getMessages(change), - gerritClientComments.getCommentData(), - gerritClientPatchSet.getRevisionBase() - ); - } + public GerritClientData getClientData(GerritChange change) { + return new GerritClientData( + gerritClientPatchSet.getFileDiffsProcessed(), + gerritClientDetail.getMessages(change), + gerritClientComments.getCommentData(), + gerritClientPatchSet.getRevisionBase()); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientPatchSet.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientPatchSet.java index 2450da0..f3b4cc7 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientPatchSet.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientPatchSet.java
@@ -1,8 +1,8 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.matchesExtensionList; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getNoEscapedGson; +import static java.util.stream.Collectors.toList; import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.ChangeInfo; @@ -15,121 +15,120 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPatchSetFileDiff; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritReviewFileDiff; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.matchesExtensionList; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getNoEscapedGson; -import static java.util.stream.Collectors.toList; - @Slf4j public class GerritClientPatchSet extends GerritClientAccount { - protected final List<String> diffs; + protected final List<String> diffs; - @Getter - protected Integer revisionBase = 0; + @Getter protected Integer revisionBase = 0; - private boolean isCommitMessage; + private boolean isCommitMessage; - public GerritClientPatchSet(Configuration config, AccountCache accountCache) { - super(config, accountCache); - diffs = new ArrayList<>(); + public GerritClientPatchSet(Configuration config, AccountCache accountCache) { + super(config, accountCache); + diffs = new ArrayList<>(); + } + + public void retrieveRevisionBase(GerritChange change) { + try (ManualRequestContext requestContext = config.openRequestContext()) { + ChangeInfo changeInfo = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .get(ListChangesOption.ALL_REVISIONS); + revisionBase = + Optional.ofNullable(changeInfo) + .map(info -> info.revisions) + .map(revisions -> revisions.size() - 1) + .orElse(0); + } catch (Exception e) { + log.error( + "Could not retrieve revisions for PatchSet with fullChangeId: {}", + change.getFullChangeId(), + e); + revisionBase = 0; } + } - public void retrieveRevisionBase(GerritChange change) { - try (ManualRequestContext requestContext = config.openRequestContext()) { - ChangeInfo changeInfo = - config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .get(ListChangesOption.ALL_REVISIONS); - revisionBase = - Optional.ofNullable(changeInfo) - .map(info -> info.revisions) - .map(revisions -> revisions.size() - 1) - .orElse(0); + protected int getChangeSetRevisionBase(ChangeSetData changeSetData) { + return isChangeSetBased(changeSetData) ? 0 : revisionBase; + } + + protected void retrieveFileDiff(GerritChange change, List<String> files, int revisionBase) + throws Exception { + List<String> enabledFileExtensions = config.getEnabledFileExtensions(); + try (ManualRequestContext requestContext = config.openRequestContext()) { + for (String filename : files) { + isCommitMessage = filename.equals("/COMMIT_MSG"); + if (!isCommitMessage && !matchesExtensionList(filename, enabledFileExtensions)) { + continue; } - catch (Exception e) { - log.error("Could not retrieve revisions for PatchSet with fullChangeId: {}", change.getFullChangeId(), e); - revisionBase = 0; - } + DiffInfo diff = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .current() + .file(filename) + .diff(revisionBase); + processFileDiff(filename, diff); + } } + } - protected int getChangeSetRevisionBase(ChangeSetData changeSetData) { - return isChangeSetBased(changeSetData) ? 0 : revisionBase; - } + private boolean isChangeSetBased(ChangeSetData changeSetData) { + return !changeSetData.getForcedReviewLastPatchSet(); + } - protected void retrieveFileDiff(GerritChange change, List<String> files, int revisionBase) throws Exception { - List<String> enabledFileExtensions = config.getEnabledFileExtensions(); - try (ManualRequestContext requestContext = config.openRequestContext()) { - for (String filename : files) { - isCommitMessage = filename.equals("/COMMIT_MSG"); - if (!isCommitMessage && !matchesExtensionList(filename, enabledFileExtensions)) { - continue; - } - DiffInfo diff = - config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .current() - .file(filename) - .diff(revisionBase); - processFileDiff(filename, diff); - } - } - } + private void processFileDiff(String filename, DiffInfo diff) { + log.debug("FileDiff content processed: {}", filename); - private boolean isChangeSetBased(ChangeSetData changeSetData) { - return !changeSetData.getForcedReviewLastPatchSet(); - } + GerritPatchSetFileDiff gerritPatchSetFileDiff = new GerritPatchSetFileDiff(); + Optional.ofNullable(diff.metaA) + .ifPresent(meta -> gerritPatchSetFileDiff.setMetaA(GerritClientPatchSet.toMeta(meta))); + Optional.ofNullable(diff.metaB) + .ifPresent(meta -> gerritPatchSetFileDiff.setMetaB(GerritClientPatchSet.toMeta(meta))); + Optional.ofNullable(diff.content) + .ifPresent( + content -> + gerritPatchSetFileDiff.setContent( + content.stream().map(GerritClientPatchSet::toContent).collect(toList()))); - private void processFileDiff(String filename, DiffInfo diff) { - log.debug("FileDiff content processed: {}", filename); + // Initialize the reduced file diff for the Gerrit review with fields `meta_a` and `meta_b` + GerritReviewFileDiff gerritReviewFileDiff = + new GerritReviewFileDiff( + gerritPatchSetFileDiff.getMetaA(), gerritPatchSetFileDiff.getMetaB()); + FileDiffProcessed fileDiffProcessed = + new FileDiffProcessed(config, isCommitMessage, gerritPatchSetFileDiff); + fileDiffsProcessed.put(filename, fileDiffProcessed); + gerritReviewFileDiff.setContent(fileDiffProcessed.getReviewDiffContent()); + diffs.add(getNoEscapedGson().toJson(gerritReviewFileDiff)); + } - GerritPatchSetFileDiff gerritPatchSetFileDiff = new GerritPatchSetFileDiff(); - Optional.ofNullable(diff.metaA) - .ifPresent( - meta -> gerritPatchSetFileDiff.setMetaA(GerritClientPatchSet.toMeta(meta))); - Optional.ofNullable(diff.metaB) - .ifPresent( - meta -> gerritPatchSetFileDiff.setMetaB(GerritClientPatchSet.toMeta(meta))); - Optional.ofNullable(diff.content) - .ifPresent( - content -> - gerritPatchSetFileDiff.setContent( - content.stream() - .map(GerritClientPatchSet::toContent) - .collect(toList()))); + protected static GerritFileDiff.Meta toMeta(DiffInfo.FileMeta input) { + GerritFileDiff.Meta meta = new GerritFileDiff.Meta(); + meta.setContentType(input.contentType); + meta.setName(input.name); + return meta; + } - // Initialize the reduced file diff for the Gerrit review with fields `meta_a` and `meta_b` - GerritReviewFileDiff gerritReviewFileDiff = new GerritReviewFileDiff(gerritPatchSetFileDiff.getMetaA(), - gerritPatchSetFileDiff.getMetaB()); - FileDiffProcessed fileDiffProcessed = new FileDiffProcessed(config, isCommitMessage, gerritPatchSetFileDiff); - fileDiffsProcessed.put(filename, fileDiffProcessed); - gerritReviewFileDiff.setContent(fileDiffProcessed.getReviewDiffContent()); - diffs.add(getNoEscapedGson().toJson(gerritReviewFileDiff)); - } - - protected static GerritFileDiff.Meta toMeta(DiffInfo.FileMeta input) { - GerritFileDiff.Meta meta = new GerritFileDiff.Meta(); - meta.setContentType(input.contentType); - meta.setName(input.name); - return meta; - } - - protected static GerritPatchSetFileDiff.Content toContent(DiffInfo.ContentEntry input) { - GerritPatchSetFileDiff.Content content = new GerritPatchSetFileDiff.Content(); - content.a = input.a; - content.b = input.b; - content.ab = input.ab; - return content; - } + protected static GerritPatchSetFileDiff.Content toContent(DiffInfo.ContentEntry input) { + GerritPatchSetFileDiff.Content content = new GerritPatchSetFileDiff.Content(); + content.a = input.a; + content.b = input.b; + content.ab = input.ab; + return content; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientReview.java index 60e5c99..3c385d2 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/gerrit/GerritClientReview.java
@@ -1,12 +1,15 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit; +import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.MessageSanitizer.sanitizeAIChatMessage; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithDoubleNewLine; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; -import com.google.gerrit.extensions.client.Comment; import com.google.gerrit.entities.LabelId; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput; import com.google.gerrit.extensions.api.changes.ReviewResult; +import com.google.gerrit.extensions.client.Comment; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.util.ManualRequestContext; import com.google.inject.Inject; @@ -17,141 +20,136 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages.DebugCodeBlocksDynamicSettings; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.review.ReviewBatch; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; - -import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.MessageSanitizer.sanitizeAIChatMessage; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithDoubleNewLine; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritClientReview extends GerritClientAccount { - private final PluginDataHandlerProvider pluginDataHandlerProvider; - private final Localizer localizer; - private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings; + private final PluginDataHandlerProvider pluginDataHandlerProvider; + private final Localizer localizer; + private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings; - @VisibleForTesting - @Inject - public GerritClientReview( - Configuration config, - AccountCache accountCache, - PluginDataHandlerProvider pluginDataHandlerProvider, - Localizer localizer - ) { - super(config, accountCache); - this.pluginDataHandlerProvider = pluginDataHandlerProvider; - this.localizer = localizer; - debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer); + @VisibleForTesting + @Inject + public GerritClientReview( + Configuration config, + AccountCache accountCache, + PluginDataHandlerProvider pluginDataHandlerProvider, + Localizer localizer) { + super(config, accountCache); + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + this.localizer = localizer; + debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer); + } + + public void setReview( + GerritChange change, + List<ReviewBatch> reviewBatches, + ChangeSetData changeSetData, + Integer reviewScore) + throws Exception { + ReviewInput reviewInput = buildReview(reviewBatches, changeSetData, reviewScore); + if (reviewInput.comments == null && reviewInput.message == null) { + return; } + try (ManualRequestContext requestContext = config.openRequestContext()) { + ReviewResult result = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .current() + .review(reviewInput); - public void setReview( - GerritChange change, - List<ReviewBatch> reviewBatches, - ChangeSetData changeSetData, - Integer reviewScore - ) throws Exception { - ReviewInput reviewInput = buildReview(reviewBatches, changeSetData, reviewScore); - if (reviewInput.comments == null && reviewInput.message == null) { - return; - } - try (ManualRequestContext requestContext = config.openRequestContext()) { - ReviewResult result = config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .current() - .review(reviewInput); - - if (!Strings.isNullOrEmpty(result.error)) { - log.error("Review setting failed with status code: {}", result.error); - } - } + if (!Strings.isNullOrEmpty(result.error)) { + log.error("Review setting failed with status code: {}", result.error); + } } + } - public void setReview( - GerritChange change, - List<ReviewBatch> reviewBatches, - ChangeSetData changeSetData - ) throws Exception { - setReview(change, reviewBatches, changeSetData, null); - } + public void setReview( + GerritChange change, List<ReviewBatch> reviewBatches, ChangeSetData changeSetData) + throws Exception { + setReview(change, reviewBatches, changeSetData, null); + } - private ReviewInput buildReview(List<ReviewBatch> reviewBatches, ChangeSetData changeSetData, Integer reviewScore) { - ReviewInput reviewInput = ReviewInput.create(); - Map<String, List<CommentInput>> comments = new HashMap<>(); - String systemMessage = localizer.getText("message.empty.review"); - if (changeSetData.getReviewSystemMessage() != null) { - systemMessage = changeSetData.getReviewSystemMessage(); - } - else if (!changeSetData.shouldHideAICodeReview()) { - comments = getReviewComments(reviewBatches); - if (reviewScore != null) { - reviewInput.label(LabelId.CODE_REVIEW, reviewScore); - } - } - updateSystemMessage(reviewInput, comments.isEmpty(), systemMessage); - if (!comments.isEmpty()) { - reviewInput.comments = comments; - } - return reviewInput; + private ReviewInput buildReview( + List<ReviewBatch> reviewBatches, ChangeSetData changeSetData, Integer reviewScore) { + ReviewInput reviewInput = ReviewInput.create(); + Map<String, List<CommentInput>> comments = new HashMap<>(); + String systemMessage = localizer.getText("message.empty.review"); + if (changeSetData.getReviewSystemMessage() != null) { + systemMessage = changeSetData.getReviewSystemMessage(); + } else if (!changeSetData.shouldHideAICodeReview()) { + comments = getReviewComments(reviewBatches); + if (reviewScore != null) { + reviewInput.label(LabelId.CODE_REVIEW, reviewScore); + } } + updateSystemMessage(reviewInput, comments.isEmpty(), systemMessage); + if (!comments.isEmpty()) { + reviewInput.comments = comments; + } + return reviewInput; + } - private void updateSystemMessage(ReviewInput reviewInput, boolean emptyComments, String systemMessage) { - List<String> messages = new ArrayList<>(); - Map<String, String> dynamicConfig = new DynamicConfiguration(pluginDataHandlerProvider).getDynamicConfig(); - if (dynamicConfig != null && !dynamicConfig.isEmpty()) { - messages.add(debugCodeBlocksDynamicSettings.getDebugCodeBlock(dynamicConfig)); - } - if (emptyComments) { - messages.add(localizer.getText("system.message.prefix") + ' ' + systemMessage); - } - if (!messages.isEmpty()) { - reviewInput.message(joinWithDoubleNewLine(messages)); - } + private void updateSystemMessage( + ReviewInput reviewInput, boolean emptyComments, String systemMessage) { + List<String> messages = new ArrayList<>(); + Map<String, String> dynamicConfig = + new DynamicConfiguration(pluginDataHandlerProvider).getDynamicConfig(); + if (dynamicConfig != null && !dynamicConfig.isEmpty()) { + messages.add(debugCodeBlocksDynamicSettings.getDebugCodeBlock(dynamicConfig)); } + if (emptyComments) { + messages.add(localizer.getText("system.message.prefix") + ' ' + systemMessage); + } + if (!messages.isEmpty()) { + reviewInput.message(joinWithDoubleNewLine(messages)); + } + } - private Map<String, List<CommentInput>> getReviewComments(List<ReviewBatch> reviewBatches) { - Map<String, List<CommentInput>> comments = new HashMap<>(); - for (ReviewBatch reviewBatch : reviewBatches) { - String message = sanitizeAIChatMessage(reviewBatch.getContent()); - if (message.trim().isEmpty()) { - log.info("Empty message from review not submitted."); - continue; - } - boolean unresolved; - String filename = reviewBatch.getFilename(); - List<CommentInput> filenameComments = comments.getOrDefault(filename, new ArrayList<>()); - CommentInput filenameComment = new CommentInput(); - filenameComment.message = message; - if (reviewBatch.getLine() != null || reviewBatch.getRange() != null) { - filenameComment.line = reviewBatch.getLine(); - Optional.ofNullable(reviewBatch.getRange()) - .ifPresent( - r -> { - Comment.Range range = new Comment.Range(); - range.startLine = r.startLine; - range.startCharacter = r.startCharacter; - range.endLine = r.endLine; - range.endCharacter = r.endCharacter; - filenameComment.range = range; - }); - filenameComment.inReplyTo = reviewBatch.getId(); - unresolved = !config.getInlineCommentsAsResolved(); - } - else { - unresolved = !config.getPatchSetCommentsAsResolved(); - } - filenameComment.unresolved = unresolved; - filenameComments.add(filenameComment); - comments.putIfAbsent(filename, filenameComments); - } - return comments; + private Map<String, List<CommentInput>> getReviewComments(List<ReviewBatch> reviewBatches) { + Map<String, List<CommentInput>> comments = new HashMap<>(); + for (ReviewBatch reviewBatch : reviewBatches) { + String message = sanitizeAIChatMessage(reviewBatch.getContent()); + if (message.trim().isEmpty()) { + log.info("Empty message from review not submitted."); + continue; + } + boolean unresolved; + String filename = reviewBatch.getFilename(); + List<CommentInput> filenameComments = comments.getOrDefault(filename, new ArrayList<>()); + CommentInput filenameComment = new CommentInput(); + filenameComment.message = message; + if (reviewBatch.getLine() != null || reviewBatch.getRange() != null) { + filenameComment.line = reviewBatch.getLine(); + Optional.ofNullable(reviewBatch.getRange()) + .ifPresent( + r -> { + Comment.Range range = new Comment.Range(); + range.startLine = r.startLine; + range.startCharacter = r.startCharacter; + range.endLine = r.endLine; + range.endCharacter = r.endCharacter; + filenameComment.range = range; + }); + filenameComment.inReplyTo = reviewBatch.getId(); + unresolved = !config.getInlineCommentsAsResolved(); + } else { + unresolved = !config.getPatchSetCommentsAsResolved(); + } + filenameComment.unresolved = unresolved; + filenameComments.add(filenameComment); + comments.putIfAbsent(filename, filenameComments); } + return comments; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatClient.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatClient.java index d4540cd..92911b6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatClient.java
@@ -1,102 +1,107 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.*; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseMessage; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseStreamed; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseUnstreamed; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatToolCall; import java.io.BufferedReader; import java.io.StringReader; import java.util.List; import java.util.Optional; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; @Slf4j -abstract public class AIChatClient extends ClientBase { - protected boolean isCommentEvent = false; - @Getter - protected String requestBody; +public abstract class AIChatClient extends ClientBase { + protected boolean isCommentEvent = false; + @Getter protected String requestBody; - public AIChatClient(Configuration config) { - super(config); - } + public AIChatClient(Configuration config) { + super(config); + } - protected AIChatResponseContent extractContent(Configuration config, String body) throws Exception { - if (config.getAIStreamOutput() && !isCommentEvent) { - StringBuilder finalContent = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new StringReader(body))) { - String line; - while ((line = reader.readLine()) != null) { - extractContentFromLine(line).ifPresent(finalContent::append); - } - } - return convertResponseContentFromJson(finalContent.toString()); + protected AIChatResponseContent extractContent(Configuration config, String body) + throws Exception { + if (config.getAIStreamOutput() && !isCommentEvent) { + StringBuilder finalContent = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new StringReader(body))) { + String line; + while ((line = reader.readLine()) != null) { + extractContentFromLine(line).ifPresent(finalContent::append); } - else { - AIChatResponseUnstreamed AIChatResponseUnstreamed = - getGson().fromJson(body, AIChatResponseUnstreamed.class); - return getResponseContent(AIChatResponseUnstreamed.getChoices().get(0).getMessage().getToolCalls()); - } + } + return convertResponseContentFromJson(finalContent.toString()); + } else { + AIChatResponseUnstreamed AIChatResponseUnstreamed = + getGson().fromJson(body, AIChatResponseUnstreamed.class); + return getResponseContent( + AIChatResponseUnstreamed.getChoices().get(0).getMessage().getToolCalls()); } + } - protected boolean validateResponse(AIChatResponseContent AIChatResponseContent, String changeId, int attemptInd) { - String returnedChangeId = AIChatResponseContent.getChangeId(); - // A response is considered valid if either no changeId is returned or the changeId returned matches the one - // provided in the request - boolean isValidated = returnedChangeId == null || changeId.equals(returnedChangeId); - if (!isValidated) { - log.error("ChangedId mismatch error (attempt #{}).\nExpected value: {}\nReturned value: {}", attemptInd, - changeId, returnedChangeId); - } - return isValidated; + protected boolean validateResponse( + AIChatResponseContent AIChatResponseContent, String changeId, int attemptInd) { + String returnedChangeId = AIChatResponseContent.getChangeId(); + // A response is considered valid if either no changeId is returned or the changeId returned + // matches the one + // provided in the request + boolean isValidated = returnedChangeId == null || changeId.equals(returnedChangeId); + if (!isValidated) { + log.error( + "ChangedId mismatch error (attempt #{}).\nExpected value: {}\nReturned value: {}", + attemptInd, + changeId, + returnedChangeId); } + return isValidated; + } - protected AIChatResponseContent getResponseContent(List<AIChatToolCall> toolCalls) { - if (toolCalls.size() > 1) { - return mergeToolCalls(toolCalls); - } else { - return getArgumentAsResponse(toolCalls, 0); - } + protected AIChatResponseContent getResponseContent(List<AIChatToolCall> toolCalls) { + if (toolCalls.size() > 1) { + return mergeToolCalls(toolCalls); + } else { + return getArgumentAsResponse(toolCalls, 0); } + } - protected Optional<String> extractContentFromLine(String line) { - String dataPrefix = "data: {\"id\""; + protected Optional<String> extractContentFromLine(String line) { + String dataPrefix = "data: {\"id\""; - if (!line.startsWith(dataPrefix)) { - return Optional.empty(); - } - AIChatResponseStreamed AIChatResponseStreamed = - getGson().fromJson(line.substring("data: ".length()), AIChatResponseStreamed.class); - AIChatResponseMessage delta = AIChatResponseStreamed.getChoices().get(0).getDelta(); - if (delta == null || delta.getToolCalls() == null) { - return Optional.empty(); - } - String content = getArgumentAsString(delta.getToolCalls(), 0); - return Optional.ofNullable(content); + if (!line.startsWith(dataPrefix)) { + return Optional.empty(); } - - private AIChatResponseContent convertResponseContentFromJson(String content) { - return getGson().fromJson(content, AIChatResponseContent.class); + AIChatResponseStreamed AIChatResponseStreamed = + getGson().fromJson(line.substring("data: ".length()), AIChatResponseStreamed.class); + AIChatResponseMessage delta = AIChatResponseStreamed.getChoices().get(0).getDelta(); + if (delta == null || delta.getToolCalls() == null) { + return Optional.empty(); } + String content = getArgumentAsString(delta.getToolCalls(), 0); + return Optional.ofNullable(content); + } - private String getArgumentAsString(List<AIChatToolCall> toolCalls, int ind) { - return toolCalls.get(ind).getFunction().getArguments(); - } + private AIChatResponseContent convertResponseContentFromJson(String content) { + return getGson().fromJson(content, AIChatResponseContent.class); + } - private AIChatResponseContent getArgumentAsResponse(List<AIChatToolCall> toolCalls, int ind) { - return convertResponseContentFromJson(getArgumentAsString(toolCalls, ind)); - } + private String getArgumentAsString(List<AIChatToolCall> toolCalls, int ind) { + return toolCalls.get(ind).getFunction().getArguments(); + } - private AIChatResponseContent mergeToolCalls(List<AIChatToolCall> toolCalls) { - AIChatResponseContent responseContent = getArgumentAsResponse(toolCalls, 0); - for (int ind = 1; ind < toolCalls.size(); ind++) { - responseContent.getReplies().addAll( - getArgumentAsResponse(toolCalls, ind).getReplies() - ); - } - return responseContent; + private AIChatResponseContent getArgumentAsResponse(List<AIChatToolCall> toolCalls, int ind) { + return convertResponseContentFromJson(getArgumentAsString(toolCalls, ind)); + } + + private AIChatResponseContent mergeToolCalls(List<AIChatToolCall> toolCalls) { + AIChatResponseContent responseContent = getArgumentAsResponse(toolCalls, 0); + for (int ind = 1; ind < toolCalls.size(); ind++) { + responseContent.getReplies().addAll(getArgumentAsResponse(toolCalls, ind).getReplies()); } + return responseContent; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatParameters.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatParameters.java index 1f4ad44..e41ea7d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatParameters.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatParameters.java
@@ -2,37 +2,37 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; - import java.util.concurrent.ThreadLocalRandom; public class AIChatParameters extends ClientBase { - private static boolean isCommentEvent; + private static boolean isCommentEvent; - public AIChatParameters(Configuration config, boolean isCommentEvent) { - super(config); - AIChatParameters.isCommentEvent = isCommentEvent; - } + public AIChatParameters(Configuration config, boolean isCommentEvent) { + super(config); + AIChatParameters.isCommentEvent = isCommentEvent; + } - public double getGptTemperature() { - if (isCommentEvent) { - return retrieveTemperature(Configuration.KEY_AI_COMMENT_TEMPERATURE, - Configuration.DEFAULT_AI_CHAT_COMMENT_TEMPERATURE); - } - else { - return retrieveTemperature(Configuration.KEY_AI_REVIEW_TEMPERATURE, - Configuration.DEFAULT_AI_CHAT_REVIEW_TEMPERATURE); - } + public double getGptTemperature() { + if (isCommentEvent) { + return retrieveTemperature( + Configuration.KEY_AI_COMMENT_TEMPERATURE, + Configuration.DEFAULT_AI_CHAT_COMMENT_TEMPERATURE); + } else { + return retrieveTemperature( + Configuration.KEY_AI_REVIEW_TEMPERATURE, + Configuration.DEFAULT_AI_CHAT_REVIEW_TEMPERATURE); } + } - public boolean getStreamOutput() { - return config.getAIStreamOutput() && !isCommentEvent; - } + public boolean getStreamOutput() { + return config.getAIStreamOutput() && !isCommentEvent; + } - public int getRandomSeed() { - return ThreadLocalRandom.current().nextInt(); - } + public int getRandomSeed() { + return ThreadLocalRandom.current().nextInt(); + } - private Double retrieveTemperature(String temperatureKey, Double defaultTemperature) { - return Double.parseDouble(config.getString(temperatureKey, String.valueOf(defaultTemperature))); - } + private Double retrieveTemperature(String temperatureKey, Double defaultTemperature) { + return Double.parseDouble(config.getString(temperatureKey, String.valueOf(defaultTemperature))); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatTools.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatTools.java index 6a74adb..bb15c8b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatTools.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/api/openai/AIChatTools.java
@@ -1,32 +1,33 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatTool; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatToolChoice; import com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils; - import java.io.IOException; import java.io.InputStreamReader; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; - public class AIChatTools { - public static AIChatTool retrieveFormatRepliesTool() { - AIChatTool tools; - try (InputStreamReader reader = FileUtils.getInputStreamReader("config/formatRepliesTool.json")) { - tools = getGson().fromJson(reader, AIChatTool.class); - } catch (IOException e) { - throw new RuntimeException("Failed to load data for ChatGPT `format_replies` tool", e); - } - return tools; + public static AIChatTool retrieveFormatRepliesTool() { + AIChatTool tools; + try (InputStreamReader reader = + FileUtils.getInputStreamReader("config/formatRepliesTool.json")) { + tools = getGson().fromJson(reader, AIChatTool.class); + } catch (IOException e) { + throw new RuntimeException("Failed to load data for ChatGPT `format_replies` tool", e); } + return tools; + } - public static AIChatToolChoice retrieveFormatRepliesToolChoice() { - AIChatToolChoice toolChoice; - try (InputStreamReader reader = FileUtils.getInputStreamReader("config/formatRepliesToolChoice.json")) { - toolChoice = getGson().fromJson(reader, AIChatToolChoice.class); - } catch (IOException e) { - throw new RuntimeException("Failed to load data for ChatGPT `format_replies` tool choice", e); - } - return toolChoice; + public static AIChatToolChoice retrieveFormatRepliesToolChoice() { + AIChatToolChoice toolChoice; + try (InputStreamReader reader = + FileUtils.getInputStreamReader("config/formatRepliesToolChoice.json")) { + toolChoice = getGson().fromJson(reader, AIChatToolChoice.class); + } catch (IOException e) { + throw new RuntimeException("Failed to load data for ChatGPT `format_replies` tool choice", e); } + return toolChoice; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/commands/ClientCommands.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/commands/ClientCommands.java index 11b6e89..42d1e70 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/commands/ClientCommands.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/commands/ClientCommands.java
@@ -7,190 +7,193 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.Directives; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; @Slf4j @Getter public class ClientCommands extends ClientBase { - private enum CommandSet { - REVIEW, - REVIEW_LAST, - DIRECTIVE, - CONFIGURE + private enum CommandSet { + REVIEW, + REVIEW_LAST, + DIRECTIVE, + CONFIGURE + } + + private enum ReviewOptionSet { + FILTER, + DEBUG + } + + private enum ConfigureOptionSet { + RESET + } + + private static final Map<String, CommandSet> COMMAND_MAP = + Map.of( + "review", CommandSet.REVIEW, + "review_last", CommandSet.REVIEW_LAST, + "directive", CommandSet.DIRECTIVE, + "configure", CommandSet.CONFIGURE); + private static final Map<String, ReviewOptionSet> REVIEW_OPTION_MAP = + Map.of( + "filter", ReviewOptionSet.FILTER, + "debug", ReviewOptionSet.DEBUG); + private static final List<CommandSet> REVIEW_COMMANDS = + new ArrayList<>(List.of(CommandSet.REVIEW, CommandSet.REVIEW_LAST)); + private static final List<CommandSet> HISTORY_COMMANDS = + new ArrayList<>(List.of(CommandSet.DIRECTIVE)); + private static final Map<String, ConfigureOptionSet> CONFIGURE_OPTION_MAP = + Map.of("reset", ConfigureOptionSet.RESET); + // Option values can be either a sequence of chars enclosed in double quotes or a sequence of + // non-space chars. + private static final String OPTION_VALUES = "\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|\\S+"; + private static final Pattern COMMAND_PATTERN = + Pattern.compile( + "/(" + + String.join("|", COMMAND_MAP.keySet()) + + ")\\b((?:\\s+--\\w+(?:=(?:" + + OPTION_VALUES + + "))?)+)?"); + private static final Pattern OPTIONS_PATTERN = + Pattern.compile("--(\\w+)(?:=(" + OPTION_VALUES + "))?"); + + private final ChangeSetData changeSetData; + private final Directives directives; + private final Localizer localizer; + + private DynamicConfiguration dynamicConfiguration; + private boolean containingHistoryCommand; + private boolean modifiedDynamicConfig; + private boolean shouldResetDynamicConfig; + + public ClientCommands( + Configuration config, + ChangeSetData changeSetData, + PluginDataHandlerProvider pluginDataHandlerProvider, + Localizer localizer) { + super(config); + this.localizer = localizer; + this.changeSetData = changeSetData; + directives = new Directives(changeSetData); + // The `dynamicConfiguration` instance is utilized only for parsing current client messages, not + // the history + if (pluginDataHandlerProvider != null) { + dynamicConfiguration = new DynamicConfiguration(pluginDataHandlerProvider); } - private enum ReviewOptionSet { - FILTER, - DEBUG + containingHistoryCommand = false; + modifiedDynamicConfig = false; + shouldResetDynamicConfig = false; + } + + public boolean parseCommands(String comment, boolean isNotHistory) { + boolean commandFound = false; + Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment); + while (reviewCommandMatcher.find()) { + CommandSet command = COMMAND_MAP.get(reviewCommandMatcher.group(1)); + parseOptions(command, reviewCommandMatcher, isNotHistory); + parseCommand(command, comment, isNotHistory); + commandFound = true; } - private enum ConfigureOptionSet { - RESET + return commandFound; + } + + public String parseRemoveCommands(String comment) { + if (parseCommands(comment, false)) { + return removeCommands(comment); } + return comment; + } - private static final Map<String, CommandSet> COMMAND_MAP = Map.of( - "review", CommandSet.REVIEW, - "review_last", CommandSet.REVIEW_LAST, - "directive", CommandSet.DIRECTIVE, - "configure", CommandSet.CONFIGURE - ); - private static final Map<String, ReviewOptionSet> REVIEW_OPTION_MAP = Map.of( - "filter", ReviewOptionSet.FILTER, - "debug", ReviewOptionSet.DEBUG - ); - private static final List<CommandSet> REVIEW_COMMANDS = new ArrayList<>(List.of( - CommandSet.REVIEW, - CommandSet.REVIEW_LAST - )); - private static final List<CommandSet> HISTORY_COMMANDS = new ArrayList<>(List.of( - CommandSet.DIRECTIVE - )); - private static final Map<String, ConfigureOptionSet> CONFIGURE_OPTION_MAP = Map.of( - "reset", ConfigureOptionSet.RESET - ); - // Option values can be either a sequence of chars enclosed in double quotes or a sequence of non-space chars. - private static final String OPTION_VALUES = "\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|\\S+"; - private static final Pattern COMMAND_PATTERN = Pattern.compile("/(" + String.join("|", - COMMAND_MAP.keySet()) + ")\\b((?:\\s+--\\w+(?:=(?:" + OPTION_VALUES + "))?)+)?"); - private static final Pattern OPTIONS_PATTERN = Pattern.compile("--(\\w+)(?:=(" + OPTION_VALUES + "))?"); + private String removeCommands(String comment) { + Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment); + return reviewCommandMatcher.replaceAll(""); + } - private final ChangeSetData changeSetData; - private final Directives directives; - private final Localizer localizer; - - private DynamicConfiguration dynamicConfiguration; - private boolean containingHistoryCommand; - private boolean modifiedDynamicConfig; - private boolean shouldResetDynamicConfig; - - public ClientCommands( - Configuration config, - ChangeSetData changeSetData, - PluginDataHandlerProvider pluginDataHandlerProvider, - Localizer localizer - ) { - super(config); - this.localizer = localizer; - this.changeSetData = changeSetData; - directives = new Directives(changeSetData); - // The `dynamicConfiguration` instance is utilized only for parsing current client messages, not the history - if (pluginDataHandlerProvider != null) { - dynamicConfiguration = new DynamicConfiguration(pluginDataHandlerProvider); + private void parseCommand(CommandSet command, String comment, boolean isNotHistory) { + if (isNotHistory) { + if (REVIEW_COMMANDS.contains(command)) { + changeSetData.setForcedReview(true); + if (command == CommandSet.REVIEW_LAST) { + log.info("Forced review command applied to the last Patch Set"); + changeSetData.setForcedReviewLastPatchSet(true); + } else { + log.info("Forced review command applied to the entire Change Set"); } - containingHistoryCommand = false; - modifiedDynamicConfig = false; - shouldResetDynamicConfig = false; - } - - public boolean parseCommands(String comment, boolean isNotHistory) { - boolean commandFound = false; - Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment); - while (reviewCommandMatcher.find()) { - CommandSet command = COMMAND_MAP.get(reviewCommandMatcher.group(1)); - parseOptions(command, reviewCommandMatcher, isNotHistory); - parseCommand(command, comment, isNotHistory); - commandFound = true; + } else if (command == CommandSet.CONFIGURE) { + if (config.getEnableMessageDebugging()) { + changeSetData.setHideAICodeReview(true); + dynamicConfiguration.updateConfiguration(modifiedDynamicConfig, shouldResetDynamicConfig); + } else { + changeSetData.setReviewSystemMessage( + localizer.getText("message.configure.from.messages.disabled")); + log.debug( + "Unable to change configuration from messages: `enableMessageDebugging` config must" + + "be set to true"); } - return commandFound; + } } + if (HISTORY_COMMANDS.contains(command)) { + containingHistoryCommand = true; + if (command == CommandSet.DIRECTIVE) { + directives.addDirective(removeCommands(comment)); + } + } + } - public String parseRemoveCommands(String comment) { - if (parseCommands(comment, false)) { - return removeCommands(comment); - } - return comment; + private void parseOptions( + CommandSet command, Matcher reviewCommandMatcher, boolean isNotHistory) { + // Command options need to be parsed only when processing the current message, not the message + // history + if (reviewCommandMatcher.group(2) == null || !isNotHistory) return; + Matcher reviewOptionsMatcher = OPTIONS_PATTERN.matcher(reviewCommandMatcher.group(2)); + while (reviewOptionsMatcher.find()) { + parseSingleOption(command, reviewOptionsMatcher); } + } - private String removeCommands(String comment) { - Matcher reviewCommandMatcher = COMMAND_PATTERN.matcher(comment); - return reviewCommandMatcher.replaceAll(""); + private void parseSingleOption(CommandSet command, Matcher reviewOptionsMatcher) { + String optionKey = reviewOptionsMatcher.group(1); + String optionValue = + Optional.ofNullable(reviewOptionsMatcher.group(2)) + .map(val -> val.replaceAll("^\"(.*)\"$", "$1")) + .orElse(""); + if (REVIEW_COMMANDS.contains(command)) { + switch (REVIEW_OPTION_MAP.get(optionKey)) { + case FILTER: + boolean value = Boolean.parseBoolean(optionValue); + log.debug("Option 'replyFilterEnabled' set to {}", value); + changeSetData.setReplyFilterEnabled(value); + break; + case DEBUG: + if (config.getEnableMessageDebugging()) { + log.debug("Response Mode set to Debug"); + changeSetData.setDebugReviewMode(true); + changeSetData.setReplyFilterEnabled(false); + } else { + changeSetData.setReviewSystemMessage( + localizer.getText("message.debugging.review.disabled")); + log.debug( + "Unable to set Response Mode to Debug: `enableMessageDebugging` config " + + "must be set to true"); + } + break; + } + } else if (command == CommandSet.CONFIGURE && config.getEnableMessageDebugging()) { + if (CONFIGURE_OPTION_MAP.get(optionKey) == ConfigureOptionSet.RESET) { + shouldResetDynamicConfig = true; + log.debug("Resetting configuration settings"); + } else { + modifiedDynamicConfig = true; + log.debug("Updating configuration setting '{}' to '{}'", optionKey, optionValue); + dynamicConfiguration.setConfig(optionKey, optionValue); + } } - - private void parseCommand(CommandSet command, String comment, boolean isNotHistory) { - if (isNotHistory) { - if (REVIEW_COMMANDS.contains(command)) { - changeSetData.setForcedReview(true); - if (command == CommandSet.REVIEW_LAST) { - log.info("Forced review command applied to the last Patch Set"); - changeSetData.setForcedReviewLastPatchSet(true); - } - else { - log.info("Forced review command applied to the entire Change Set"); - } - } - else if (command == CommandSet.CONFIGURE) { - if (config.getEnableMessageDebugging()) { - changeSetData.setHideAICodeReview(true); - dynamicConfiguration.updateConfiguration(modifiedDynamicConfig, shouldResetDynamicConfig); - } - else { - changeSetData.setReviewSystemMessage(localizer.getText( - "message.configure.from.messages.disabled" - )); - log.debug("Unable to change configuration from messages: `enableMessageDebugging` config must" + - "be set to true"); - } - } - } - if (HISTORY_COMMANDS.contains(command)) { - containingHistoryCommand = true; - if (command == CommandSet.DIRECTIVE) { - directives.addDirective(removeCommands(comment)); - } - } - } - - private void parseOptions(CommandSet command, Matcher reviewCommandMatcher, boolean isNotHistory) { - // Command options need to be parsed only when processing the current message, not the message history - if (reviewCommandMatcher.group(2) == null || !isNotHistory) return; - Matcher reviewOptionsMatcher = OPTIONS_PATTERN.matcher(reviewCommandMatcher.group(2)); - while (reviewOptionsMatcher.find()) { - parseSingleOption(command, reviewOptionsMatcher); - } - } - - private void parseSingleOption(CommandSet command, Matcher reviewOptionsMatcher) { - String optionKey = reviewOptionsMatcher.group(1); - String optionValue = Optional.ofNullable(reviewOptionsMatcher.group(2)) - .map(val -> val.replaceAll("^\"(.*)\"$", "$1")) - .orElse(""); - if (REVIEW_COMMANDS.contains(command)) { - switch (REVIEW_OPTION_MAP.get(optionKey)) { - case FILTER: - boolean value = Boolean.parseBoolean(optionValue); - log.debug("Option 'replyFilterEnabled' set to {}", value); - changeSetData.setReplyFilterEnabled(value); - break; - case DEBUG: - if (config.getEnableMessageDebugging()) { - log.debug("Response Mode set to Debug"); - changeSetData.setDebugReviewMode(true); - changeSetData.setReplyFilterEnabled(false); - } else { - changeSetData.setReviewSystemMessage(localizer.getText( - "message.debugging.review.disabled" - )); - log.debug("Unable to set Response Mode to Debug: `enableMessageDebugging` config " + - "must be set to true"); - } - break; - } - } else if (command == CommandSet.CONFIGURE && config.getEnableMessageDebugging()) { - if (CONFIGURE_OPTION_MAP.get(optionKey) == ConfigureOptionSet.RESET) { - shouldResetDynamicConfig = true; - log.debug("Resetting configuration settings"); - } - else { - modifiedDynamicConfig = true; - log.debug("Updating configuration setting '{}' to '{}'", optionKey, optionValue); - dynamicConfiguration.setConfig(optionKey, optionValue); - } - } - } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClient.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClient.java index 6d87ae8..005482a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClient.java
@@ -1,70 +1,77 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.http; -import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import org.apache.http.NameValuePair; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import java.io.IOException; import java.util.Map; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.apache.http.NameValuePair; @Slf4j public class HttpClient { - private final OkHttpClient client = new OkHttpClient(); + private final OkHttpClient client = new OkHttpClient(); - public String execute(Request request) { - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - log.debug("HttpClient Response body: {}", response.body()); - if (response.body() != null) { - return response.body().string(); - } - else { - log.error("Request {} returned an empty string", request); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return null; + public String execute(Request request) { + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + log.debug("HttpClient Response body: {}", response.body()); + if (response.body() != null) { + return response.body().string(); + } else { + log.error("Request {} returned an empty string", request); + } + } catch (IOException e) { + throw new RuntimeException(e); } + return null; + } - public Request createRequest(String uri, Configuration configuration, RequestBody body, Map<String, String> additionalHeaders) { - // If body is null, a GET request is initiated. Otherwise, a POST request is sent with the specified body. - Request.Builder builder = new Request.Builder() - .url(uri); + public Request createRequest( + String uri, + Configuration configuration, + RequestBody body, + Map<String, String> additionalHeaders) { + // If body is null, a GET request is initiated. Otherwise, a POST request is sent with the + // specified body. + Request.Builder builder = new Request.Builder().url(uri); - // depending on the aiType, add appropriate authorization header ( if required ). - NameValuePair authHeader = configuration.getAuthorizationHeaderInfo(); - if (authHeader != null) { - builder.header(authHeader.getName(), authHeader.getValue()); - } - if (body != null) { - builder.post(body); - } - else { - builder.get(); - } - if (additionalHeaders != null) { - for (Map.Entry<String, String> header : additionalHeaders.entrySet()) { - builder.header(header.getKey(), header.getValue()); - } - } - return builder.build(); + // depending on the aiType, add appropriate authorization header ( if required ). + NameValuePair authHeader = configuration.getAuthorizationHeaderInfo(); + if (authHeader != null) { + builder.header(authHeader.getName(), authHeader.getValue()); } - - public Request createRequestFromJson(String uri, Configuration configuration, Object requestObject, - Map<String, String> additionalHeaders) { - if (requestObject != null) { - String bodyJson = getGson().toJson(requestObject); - log.debug("Request body: {}", bodyJson); - RequestBody body = RequestBody.create(bodyJson, MediaType.get("application/json")); - - return createRequest(uri, configuration, body, additionalHeaders); - } - else { - return createRequest(uri, configuration, null, additionalHeaders); - } + if (body != null) { + builder.post(body); + } else { + builder.get(); } + if (additionalHeaders != null) { + for (Map.Entry<String, String> header : additionalHeaders.entrySet()) { + builder.header(header.getKey(), header.getValue()); + } + } + return builder.build(); + } + + public Request createRequestFromJson( + String uri, + Configuration configuration, + Object requestObject, + Map<String, String> additionalHeaders) { + if (requestObject != null) { + String bodyJson = getGson().toJson(requestObject); + log.debug("Request body: {}", bodyJson); + RequestBody body = RequestBody.create(bodyJson, MediaType.get("application/json")); + + return createRequest(uri, configuration, body, additionalHeaders); + } else { + return createRequest(uri, configuration, null, additionalHeaders); + } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClientWithRetry.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClientWithRetry.java index af6334d..85cf167 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClientWithRetry.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/http/HttpClientWithRetry.java
@@ -1,56 +1,65 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.http; -import com.github.rholder.retry.*; -import com.google.inject.Singleton; -import lombok.extern.slf4j.Slf4j; +import static java.net.HttpURLConnection.HTTP_OK; +import com.github.rholder.retry.Attempt; +import com.github.rholder.retry.RetryException; +import com.github.rholder.retry.RetryListener; +import com.github.rholder.retry.Retryer; +import com.github.rholder.retry.RetryerBuilder; +import com.github.rholder.retry.StopStrategies; +import com.github.rholder.retry.WaitStrategies; +import com.google.inject.Singleton; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; - -import static java.net.HttpURLConnection.HTTP_OK; +import lombok.extern.slf4j.Slf4j; @Singleton @Slf4j public class HttpClientWithRetry { - private final Retryer<HttpResponse<String>> retryer; + private final Retryer<HttpResponse<String>> retryer; - private final HttpClient httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofMinutes(5)) - .build(); + private final HttpClient httpClient = + HttpClient.newBuilder().connectTimeout(Duration.ofMinutes(5)).build(); - public HttpClientWithRetry() { - //Attention, 'com.github.rholder.retry.RetryListener' is marked unstable with @Beta annotation - RetryListener listener = new RetryListener() { - @Override - public <V> void onRetry(Attempt<V> attempt) { - if (attempt.hasException()) { - log.error("Retry failed with exception: " + attempt.getExceptionCause()); - } + public HttpClientWithRetry() { + // Attention, 'com.github.rholder.retry.RetryListener' is marked unstable with @Beta annotation + RetryListener listener = + new RetryListener() { + @Override + public <V> void onRetry(Attempt<V> attempt) { + if (attempt.hasException()) { + log.error("Retry failed with exception: " + attempt.getExceptionCause()); } + } }; - this.retryer = RetryerBuilder.<HttpResponse<String>>newBuilder() - .retryIfException() - .retryIfResult(response -> { - if (response.statusCode() != HTTP_OK) { - log.error("Retry because HTTP status code is not 200. The status code is: " + response.statusCode()); - return true; - } else { - return false; - } - }).withWaitStrategy(WaitStrategies.fixedWait(20, TimeUnit.SECONDS)) - .withStopStrategy(StopStrategies.stopAfterAttempt(5)) - .withRetryListener(listener) - .build(); - } + this.retryer = + RetryerBuilder.<HttpResponse<String>>newBuilder() + .retryIfException() + .retryIfResult( + response -> { + if (response.statusCode() != HTTP_OK) { + log.error( + "Retry because HTTP status code is not 200. The status code is: " + + response.statusCode()); + return true; + } else { + return false; + } + }) + .withWaitStrategy(WaitStrategies.fixedWait(20, TimeUnit.SECONDS)) + .withStopStrategy(StopStrategies.stopAfterAttempt(5)) + .withRetryListener(listener) + .build(); + } - public HttpResponse<String> execute(HttpRequest request) throws ExecutionException, RetryException { - return retryer.call(() -> httpClient.send(request, HttpResponse.BodyHandlers.ofString())); - } - + public HttpResponse<String> execute(HttpRequest request) + throws ExecutionException, RetryException { + return retryer.call(() -> httpClient.send(request, HttpResponse.BodyHandlers.ofString())); + } } -
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/ClientMessage.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/ClientMessage.java index 689c64d..11f3cd6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/ClientMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/ClientMessage.java
@@ -6,103 +6,104 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.commands.ClientCommands; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - @Slf4j public class ClientMessage extends ClientBase { - private static final Pattern MESSAGE_HEADING_PATTERN = Pattern.compile( - "^(?:Patch Set \\d+:[^\\n]*\\s+(?:\\(\\d+ comments?\\)\\s*)?)+"); + private static final Pattern MESSAGE_HEADING_PATTERN = + Pattern.compile("^(?:Patch Set \\d+:[^\\n]*\\s+(?:\\(\\d+ comments?\\)\\s*)?)+"); - private final Pattern botMentionPattern; - private final ClientCommands clientCommands; - private final DebugCodeBlocksReview debugCodeBlocksReview; - private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings; + private final Pattern botMentionPattern; + private final ClientCommands clientCommands; + private final DebugCodeBlocksReview debugCodeBlocksReview; + private final DebugCodeBlocksDynamicSettings debugCodeBlocksDynamicSettings; - @Getter - private String message; + @Getter private String message; - public ClientMessage( - Configuration config, - ChangeSetData changeSetData, - PluginDataHandlerProvider pluginDataHandlerProvider, - Localizer localizer - ) { - super(config); - botMentionPattern = getBotMentionPattern(); - clientCommands = new ClientCommands(config, changeSetData, pluginDataHandlerProvider, localizer); - debugCodeBlocksReview = new DebugCodeBlocksReview(localizer); - debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer); + public ClientMessage( + Configuration config, + ChangeSetData changeSetData, + PluginDataHandlerProvider pluginDataHandlerProvider, + Localizer localizer) { + super(config); + botMentionPattern = getBotMentionPattern(); + clientCommands = + new ClientCommands(config, changeSetData, pluginDataHandlerProvider, localizer); + debugCodeBlocksReview = new DebugCodeBlocksReview(localizer); + debugCodeBlocksDynamicSettings = new DebugCodeBlocksDynamicSettings(localizer); + } + + public ClientMessage( + Configuration config, ChangeSetData changeSetData, String message, Localizer localizer) { + this(config, changeSetData, (PluginDataHandlerProvider) null, localizer); + this.message = message; + } + + public boolean isBotAddressed(String message) { + log.debug("Processing comment: {}", message); + Matcher userMatcher = botMentionPattern.matcher(message); + if (!userMatcher.find()) { + log.debug( + "Skipping action since the comment does not mention the ChatGPT bot." + + " Expected bot name in comment: {}, Actual comment text: {}", + config.getGerritUserName(), + message); + return false; } + return true; + } - public ClientMessage(Configuration config, ChangeSetData changeSetData, String message, Localizer localizer) { - this(config, changeSetData, (PluginDataHandlerProvider) null, localizer); - this.message = message; - } + public ClientMessage removeHeadings() { + message = MESSAGE_HEADING_PATTERN.matcher(message).replaceAll(""); + return this; + } - public boolean isBotAddressed(String message) { - log.debug("Processing comment: {}", message); - Matcher userMatcher = botMentionPattern.matcher(message); - if (!userMatcher.find()) { - log.debug("Skipping action since the comment does not mention the ChatGPT bot." + - " Expected bot name in comment: {}, Actual comment text: {}", - config.getGerritUserName(), message); - return false; - } - return true; - } + public ClientMessage removeMentions() { + message = botMentionPattern.matcher(message).replaceAll("").trim(); + return this; + } - public ClientMessage removeHeadings() { - message = MESSAGE_HEADING_PATTERN.matcher(message).replaceAll(""); - return this; - } + public ClientMessage parseRemoveCommands() { + message = clientCommands.parseRemoveCommands(message); + return this; + } - public ClientMessage removeMentions() { - message = botMentionPattern.matcher(message).replaceAll("").trim(); - return this; - } + public ClientMessage removeDebugCodeBlocksReview() { + message = debugCodeBlocksReview.removeDebugCodeBlocks(message); + return this; + } - public ClientMessage parseRemoveCommands() { - message = clientCommands.parseRemoveCommands(message); - return this; - } + public ClientMessage removeDebugCodeBlocksDynamicSettings() { + message = debugCodeBlocksDynamicSettings.removeDebugCodeBlocks(message); + return this; + } - public ClientMessage removeDebugCodeBlocksReview() { - message = debugCodeBlocksReview.removeDebugCodeBlocks(message); - return this; - } + public boolean isContainingHistoryCommand() { + return clientCommands.isContainingHistoryCommand(); + } - public ClientMessage removeDebugCodeBlocksDynamicSettings() { - message = debugCodeBlocksDynamicSettings.removeDebugCodeBlocks(message); - return this; - } + public boolean parseCommands(String comment, boolean isNotHistory) { + return clientCommands.parseCommands(comment, isNotHistory); + } - public boolean isContainingHistoryCommand() { - return clientCommands.isContainingHistoryCommand(); - } + public void processHistoryCommand() { + clientCommands.getDirectives().copyDirectiveToSettings(); + } - public boolean parseCommands(String comment, boolean isNotHistory) { - return clientCommands.parseCommands(comment, isNotHistory); - } + private Pattern getBotMentionPattern() { + String emailRegex = "^(?!>).*?(?:@" + getUserNameOrEmail() + ")\\b"; + return Pattern.compile(emailRegex, Pattern.MULTILINE); + } - public void processHistoryCommand() { - clientCommands.getDirectives().copyDirectiveToSettings(); + private String getUserNameOrEmail() { + String escapedUserName = Pattern.quote(config.getGerritUserName()); + String userEmail = config.getGerritUserEmail(); + if (userEmail.isBlank()) { + return escapedUserName; } - - private Pattern getBotMentionPattern() { - String emailRegex = "^(?!>).*?(?:@" + getUserNameOrEmail() + ")\\b"; - return Pattern.compile(emailRegex, Pattern.MULTILINE); - } - - private String getUserNameOrEmail() { - String escapedUserName = Pattern.quote(config.getGerritUserName()); - String userEmail = config.getGerritUserEmail(); - if (userEmail.isBlank()) { - return escapedUserName; - } - return escapedUserName + "|" + Pattern.quote(userEmail); - } + return escapedUserName + "|" + Pattern.quote(userEmail); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocks.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocks.java index 8894a33..b4ebe64 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocks.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocks.java
@@ -1,32 +1,39 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.CODE_DELIMITER; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.CODE_DELIMITER_BEGIN; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; + import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; - public class DebugCodeBlocks { - protected final String commentOpening; - protected final Pattern debugMessagePattern; + protected final String commentOpening; + protected final Pattern debugMessagePattern; - public DebugCodeBlocks(String openingTitle) { - commentOpening = CODE_DELIMITER_BEGIN + openingTitle + "\n"; - debugMessagePattern = Pattern.compile("\\s+" + CODE_DELIMITER +"\\s*" + openingTitle + ".*" + - CODE_DELIMITER + "\\s*", Pattern.DOTALL); - } + public DebugCodeBlocks(String openingTitle) { + commentOpening = CODE_DELIMITER_BEGIN + openingTitle + "\n"; + debugMessagePattern = + Pattern.compile( + "\\s+" + CODE_DELIMITER + "\\s*" + openingTitle + ".*" + CODE_DELIMITER + "\\s*", + Pattern.DOTALL); + } - public String removeDebugCodeBlocks(String message) { - Matcher debugMessagematcher = debugMessagePattern.matcher(message); - return debugMessagematcher.replaceAll(""); - } + public String removeDebugCodeBlocks(String message) { + Matcher debugMessagematcher = debugMessagePattern.matcher(message); + return debugMessagematcher.replaceAll(""); + } - protected String getDebugCodeBlock(List<String> panelItems) { - return joinWithNewLine(new ArrayList<>() {{ + protected String getDebugCodeBlock(List<String> panelItems) { + return joinWithNewLine( + new ArrayList<>() { + { add(commentOpening); addAll(panelItems); add(CODE_DELIMITER); - }}); - } + } + }); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java index 0c57376..6b59477 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksDynamicSettings.java
@@ -1,20 +1,17 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages; -import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.prettyStringifyMap; +import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; import java.util.List; import java.util.Map; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.prettyStringifyMap; - public class DebugCodeBlocksDynamicSettings extends DebugCodeBlocks { - public DebugCodeBlocksDynamicSettings(Localizer localizer) { - super(localizer.getText("message.dynamic.configuration.title")); - } + public DebugCodeBlocksDynamicSettings(Localizer localizer) { + super(localizer.getText("message.dynamic.configuration.title")); + } - public String getDebugCodeBlock(Map<String, String> dynamicConfig) { - return super.getDebugCodeBlock(List.of( - prettyStringifyMap(dynamicConfig) - )); - } + public String getDebugCodeBlock(Map<String, String> dynamicConfig) { + return super.getDebugCodeBlock(List.of(prettyStringifyMap(dynamicConfig))); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksReview.java index 2832cb2..083fe45 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/messages/DebugCodeBlocksReview.java
@@ -1,23 +1,20 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages; -import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; - -import java.util.List; - import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.prettyStringifyObject; +import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; +import java.util.List; + public class DebugCodeBlocksReview extends DebugCodeBlocks { - private static final String HIDDEN_REPLY = "hidden: %s"; + private static final String HIDDEN_REPLY = "hidden: %s"; - public DebugCodeBlocksReview(Localizer localizer) { - super(localizer.getText("message.debugging.review.title")); - } + public DebugCodeBlocksReview(Localizer localizer) { + super(localizer.getText("message.debugging.review.title")); + } - public String getDebugCodeBlock(AIChatReplyItem replyItem, boolean isHidden) { - return super.getDebugCodeBlock(List.of( - String.format(HIDDEN_REPLY, isHidden), - prettyStringifyObject(replyItem) - )); - } + public String getDebugCodeBlock(AIChatReplyItem replyItem, boolean isHidden) { + return super.getDebugCodeBlock( + List.of(String.format(HIDDEN_REPLY, isHidden), prettyStringifyObject(replyItem))); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/CodeFinder.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/CodeFinder.java index e02a899..da41a7a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/CodeFinder.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/CodeFinder.java
@@ -1,143 +1,162 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.code; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritCodeRange; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.code.CodeFinderDiff; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.diff.DiffContent; -import lombok.extern.slf4j.Slf4j; - import java.lang.reflect.Field; import java.util.List; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; @Slf4j public class CodeFinder { - private static final String PUNCTUATION_REGEX = "([()\\[\\]{}<>:;,?&+\\-*/%|=])"; - private static final String BEGINNING_DIFF_REGEX = "(?:^|\n)[+\\-]"; - private static final String ENDING_ELLIPSIS_REGEX = "\\.\\.\\.\\W*$"; + private static final String PUNCTUATION_REGEX = "([()\\[\\]{}<>:;,?&+\\-*/%|=])"; + private static final String BEGINNING_DIFF_REGEX = "(?:^|\n)[+\\-]"; + private static final String ENDING_ELLIPSIS_REGEX = "\\.\\.\\.\\W*$"; - private final String NON_PRINTING_REPLACEMENT; - private final String PUNCTUATION_REPLACEMENT; - private final String PLACEHOLDER_REGEX; - private final List<CodeFinderDiff> codeFinderDiffs; + private final String NON_PRINTING_REPLACEMENT; + private final String PUNCTUATION_REPLACEMENT; + private final String PLACEHOLDER_REGEX; + private final List<CodeFinderDiff> codeFinderDiffs; - private int commentedLine; - private Pattern commentedCodePattern; - private GerritCodeRange currentCodeRange; - private GerritCodeRange closestCodeRange; + private int commentedLine; + private Pattern commentedCodePattern; + private GerritCodeRange currentCodeRange; + private GerritCodeRange closestCodeRange; - public CodeFinder(List<CodeFinderDiff> codeFinderDiffs, String randomPlaceholder) { - this.codeFinderDiffs = codeFinderDiffs; - NON_PRINTING_REPLACEMENT = "\\\\E" + randomPlaceholder +"\\\\Q"; - PUNCTUATION_REPLACEMENT = "\\\\E" + randomPlaceholder +"\\\\$1" + randomPlaceholder +"\\\\Q"; - PLACEHOLDER_REGEX = "(?:" + randomPlaceholder + ")+"; - } + public CodeFinder(List<CodeFinderDiff> codeFinderDiffs, String randomPlaceholder) { + this.codeFinderDiffs = codeFinderDiffs; + NON_PRINTING_REPLACEMENT = "\\\\E" + randomPlaceholder + "\\\\Q"; + PUNCTUATION_REPLACEMENT = "\\\\E" + randomPlaceholder + "\\\\$1" + randomPlaceholder + "\\\\Q"; + PLACEHOLDER_REGEX = "(?:" + randomPlaceholder + ")+"; + } - public GerritCodeRange findCommentedCode(AIChatReplyItem replyItem, int commentedLine) { - this.commentedLine = commentedLine; - updateCodePattern(replyItem); - currentCodeRange = null; - closestCodeRange = null; - for (CodeFinderDiff codeFinderDiff : codeFinderDiffs) { - for (Field diffField : DiffContent.class.getDeclaredFields()) { - String diffCode = getDiffItem(diffField, codeFinderDiff.getContent()); - if (diffCode != null) { - TreeMap<Integer, Integer> charToLineMapItem = codeFinderDiff.getCharToLineMap(); - try { - findCodeLines(diffCode, charToLineMapItem); - } - catch (IllegalArgumentException e) { - log.warn("Could not retrieve line number from charToLineMap.\nDiff Code = {}", diffCode, e); - } - } - } + public GerritCodeRange findCommentedCode(AIChatReplyItem replyItem, int commentedLine) { + this.commentedLine = commentedLine; + updateCodePattern(replyItem); + currentCodeRange = null; + closestCodeRange = null; + for (CodeFinderDiff codeFinderDiff : codeFinderDiffs) { + for (Field diffField : DiffContent.class.getDeclaredFields()) { + String diffCode = getDiffItem(diffField, codeFinderDiff.getContent()); + if (diffCode != null) { + TreeMap<Integer, Integer> charToLineMapItem = codeFinderDiff.getCharToLineMap(); + try { + findCodeLines(diffCode, charToLineMapItem); + } catch (IllegalArgumentException e) { + log.warn( + "Could not retrieve line number from charToLineMap.\nDiff Code = {}", diffCode, e); + } } - - return closestCodeRange; + } } - private void updateCodePattern(AIChatReplyItem replyItem) { - String commentedCode = replyItem.getCodeSnippet() - .replaceAll(BEGINNING_DIFF_REGEX, "") - .replaceAll(ENDING_ELLIPSIS_REGEX, "") - .trim(); - String commentedCodeRegex = Pattern.quote(commentedCode); - // Generalize the regex to capture snippets where existing sequences of non-printing chars have been modified - // from the original code - commentedCodeRegex = commentedCodeRegex.replaceAll("\\s+", NON_PRINTING_REPLACEMENT); - // Generalize the regex to capture snippets where non-printing chars have been removed from around the - // punctuation marks of the original code - commentedCodeRegex = commentedCodeRegex.replaceAll(PUNCTUATION_REGEX, PUNCTUATION_REPLACEMENT); - // Remove redundant empty literal escape sequences that could have resulted from previous substitutions - commentedCodeRegex = commentedCodeRegex.replaceAll("\\\\Q\\\\E", ""); - // Obtain a functional regex to match code snippets without relying on non-printing chars - commentedCodeRegex = commentedCodeRegex.replaceAll(PLACEHOLDER_REGEX, "\\\\s*"); - // Remove any detected trailing matching sequence of non-printing chars - commentedCodeRegex = commentedCodeRegex.replaceAll("\\\\s\\*$", ""); - commentedCodePattern = Pattern.compile(commentedCodeRegex); - } + return closestCodeRange; + } - private double calcCodeDistance(GerritCodeRange range, int fromLine) { - return Math.abs((range.endLine - range.startLine) / 2 - fromLine); - } + private void updateCodePattern(AIChatReplyItem replyItem) { + String commentedCode = + replyItem + .getCodeSnippet() + .replaceAll(BEGINNING_DIFF_REGEX, "") + .replaceAll(ENDING_ELLIPSIS_REGEX, "") + .trim(); + String commentedCodeRegex = Pattern.quote(commentedCode); + // Generalize the regex to capture snippets where existing sequences of non-printing chars have + // been modified + // from the original code + commentedCodeRegex = commentedCodeRegex.replaceAll("\\s+", NON_PRINTING_REPLACEMENT); + // Generalize the regex to capture snippets where non-printing chars have been removed from + // around the + // punctuation marks of the original code + commentedCodeRegex = commentedCodeRegex.replaceAll(PUNCTUATION_REGEX, PUNCTUATION_REPLACEMENT); + // Remove redundant empty literal escape sequences that could have resulted from previous + // substitutions + commentedCodeRegex = commentedCodeRegex.replaceAll("\\\\Q\\\\E", ""); + // Obtain a functional regex to match code snippets without relying on non-printing chars + commentedCodeRegex = commentedCodeRegex.replaceAll(PLACEHOLDER_REGEX, "\\\\s*"); + // Remove any detected trailing matching sequence of non-printing chars + commentedCodeRegex = commentedCodeRegex.replaceAll("\\\\s\\*$", ""); + commentedCodePattern = Pattern.compile(commentedCodeRegex); + } - private String getDiffItem(Field diffField, DiffContent diffItem) { - try { - return (String) diffField.get(diffItem); - } - catch (IllegalAccessException e) { - log.error("Error while processing file difference (diff type: {})", diffField.getName(), e); - return null; - } - } + private double calcCodeDistance(GerritCodeRange range, int fromLine) { + return Math.abs((range.endLine - range.startLine) / 2 - fromLine); + } - private int getLineNumber(TreeMap<Integer, Integer> charToLineMapItem, int position) { - Integer floorPosition = charToLineMapItem.floorKey(position); - if (floorPosition == null) { - throw new IllegalArgumentException("Position: " + position); - } - return charToLineMapItem.get(floorPosition); + private String getDiffItem(Field diffField, DiffContent diffItem) { + try { + return (String) diffField.get(diffItem); + } catch (IllegalAccessException e) { + log.error("Error while processing file difference (diff type: {})", diffField.getName(), e); + return null; } + } - private int getLineCharacter(String diffCode, int position) { - // Return the offset relative to the nearest preceding newline character if found, `position` otherwise - return position - diffCode.substring(0, position).lastIndexOf("\n") -1; + private int getLineNumber(TreeMap<Integer, Integer> charToLineMapItem, int position) { + Integer floorPosition = charToLineMapItem.floorKey(position); + if (floorPosition == null) { + throw new IllegalArgumentException("Position: " + position); } + return charToLineMapItem.get(floorPosition); + } - private void findCodeLines(String diffCode, TreeMap<Integer, Integer> charToLineMapItem) - throws IllegalArgumentException { - Matcher codeMatcher = commentedCodePattern.matcher(diffCode); - while (codeMatcher.find()) { - int startPosition = codeMatcher.start(); - int endPosition = codeMatcher.end(); - int startLine = getLineNumber(charToLineMapItem, startPosition); - int endLine = getLineNumber(charToLineMapItem, endPosition); - if (startLine > endLine) { - log.info("Code range discarded: start line ({}) greater than end line ({}).\ncodeMatcher: {}.\n" + - "diffCode: {}", startLine, endLine, codeMatcher, diffCode); - continue; - } - int startCharacter = getLineCharacter(diffCode, startPosition); - int endCharacter = getLineCharacter(diffCode, endPosition); - if (startLine == endLine && startCharacter > endCharacter) { - log.info("Code range discarded: start char ({}) greater than end char ({}) for line {}.\ncodeMatcher:" + - " {}.\ndiffCode: {}", startCharacter, endCharacter, startLine, codeMatcher, diffCode); - continue; - } - currentCodeRange = GerritCodeRange.builder() - .startLine(startLine) - .endLine(endLine) - .startCharacter(startCharacter) - .endCharacter(endCharacter) - .build(); - // If multiple commented code portions are found and currentCommentRange is closer to the line - // number suggested by ChatGPT than closestCommentRange, it becomes the new closestCommentRange - if (closestCodeRange == null || calcCodeDistance(currentCodeRange, commentedLine) < - calcCodeDistance(closestCodeRange, commentedLine)) { - closestCodeRange = currentCodeRange.toBuilder().build(); - } - } + private int getLineCharacter(String diffCode, int position) { + // Return the offset relative to the nearest preceding newline character if found, `position` + // otherwise + return position - diffCode.substring(0, position).lastIndexOf("\n") - 1; + } + + private void findCodeLines(String diffCode, TreeMap<Integer, Integer> charToLineMapItem) + throws IllegalArgumentException { + Matcher codeMatcher = commentedCodePattern.matcher(diffCode); + while (codeMatcher.find()) { + int startPosition = codeMatcher.start(); + int endPosition = codeMatcher.end(); + int startLine = getLineNumber(charToLineMapItem, startPosition); + int endLine = getLineNumber(charToLineMapItem, endPosition); + if (startLine > endLine) { + log.info( + "Code range discarded: start line ({}) greater than end line ({}).\ncodeMatcher: {}.\n" + + "diffCode: {}", + startLine, + endLine, + codeMatcher, + diffCode); + continue; + } + int startCharacter = getLineCharacter(diffCode, startPosition); + int endCharacter = getLineCharacter(diffCode, endPosition); + if (startLine == endLine && startCharacter > endCharacter) { + log.info( + "Code range discarded: start char ({}) greater than end char ({}) for line {}.\n" + + "codeMatcher: {}.\n" + + "diffCode: {}", + startCharacter, + endCharacter, + startLine, + codeMatcher, + diffCode); + continue; + } + currentCodeRange = + GerritCodeRange.builder() + .startLine(startLine) + .endLine(endLine) + .startCharacter(startCharacter) + .endCharacter(endCharacter) + .build(); + // If multiple commented code portions are found and currentCommentRange is closer to the line + // number suggested by ChatGPT than closestCommentRange, it becomes the new + // closestCommentRange + if (closestCodeRange == null + || calcCodeDistance(currentCodeRange, commentedLine) + < calcCodeDistance(closestCodeRange, commentedLine)) { + closestCodeRange = currentCodeRange.toBuilder().build(); + } } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/InlineCode.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/InlineCode.java index 4fa8382..187e56f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/InlineCode.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/code/InlineCode.java
@@ -1,89 +1,86 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.code; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; + import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritCodeRange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; -import lombok.extern.slf4j.Slf4j; - +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import java.util.ArrayList; import java.util.List; import java.util.Optional; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; +import lombok.extern.slf4j.Slf4j; @Slf4j public class InlineCode { - private final CodeFinder codeFinder; - private final List<String> newContent; - private GerritCodeRange range; + private final CodeFinder codeFinder; + private final List<String> newContent; + private GerritCodeRange range; - public InlineCode(FileDiffProcessed fileDiffProcessed) { - codeFinder = new CodeFinder(fileDiffProcessed.getCodeFinderDiffs(), fileDiffProcessed.getRandomPlaceholder()); - newContent = fileDiffProcessed.getNewContent(); + public InlineCode(FileDiffProcessed fileDiffProcessed) { + codeFinder = + new CodeFinder( + fileDiffProcessed.getCodeFinderDiffs(), fileDiffProcessed.getRandomPlaceholder()); + newContent = fileDiffProcessed.getNewContent(); + } + + public String getInlineCode(GerritComment commentProperty) { + if (commentProperty.getRange() != null) { + List<String> codeByRange = new ArrayList<>(); + range = commentProperty.getRange(); + for (int line_num = range.startLine; line_num <= range.endLine; line_num++) { + codeByRange.add(getLineSlice(line_num)); + } + return joinWithNewLine(codeByRange); + } else { + return getLineFromLineNumber(commentProperty.getLine()); + } + } + + public Optional<GerritCodeRange> findCommentRange(AIChatReplyItem replyItem) { + int commentedLine; + try { + commentedLine = replyItem.getLineNumber(); + } catch (NumberFormatException ex) { + // If the line number is not passed, a line in the middle of the code is used as best guess + commentedLine = newContent.size() / 2; } - public String getInlineCode(GerritComment commentProperty) { - if (commentProperty.getRange() != null) { - List<String> codeByRange = new ArrayList<>(); - range = commentProperty.getRange(); - for (int line_num = range.startLine; line_num <= range.endLine; line_num++) { - codeByRange.add(getLineSlice(line_num)); - } - return joinWithNewLine(codeByRange); - } - else { - return getLineFromLineNumber(commentProperty.getLine()); - } - } + return Optional.ofNullable(codeFinder.findCommentedCode(replyItem, commentedLine)); + } - public Optional<GerritCodeRange> findCommentRange(AIChatReplyItem replyItem) { - int commentedLine; - try { - commentedLine = replyItem.getLineNumber(); - } - catch (NumberFormatException ex){ - // If the line number is not passed, a line in the middle of the code is used as best guess - commentedLine = newContent.size() / 2; - } - - return Optional.ofNullable(codeFinder.findCommentedCode(replyItem, commentedLine)); + private String getLineSlice(int line_num) { + String line = getLineFromLineNumber(line_num); + if (line == null) { + throw new RuntimeException("Error retrieving line number from content"); } - - private String getLineSlice(int line_num) { - String line = getLineFromLineNumber(line_num); - if (line == null) { - throw new RuntimeException("Error retrieving line number from content"); - } - try { - if (line_num == range.endLine) { - line = line.substring(0, range.endCharacter); - } - if (line_num == range.startLine) { - line = line.substring(range.startCharacter); - } - } - catch (StringIndexOutOfBoundsException e) { - log.info("Could not extract a slice from line \"{}\". The whole line is returned", line); - } - return line; + try { + if (line_num == range.endLine) { + line = line.substring(0, range.endCharacter); + } + if (line_num == range.startLine) { + line = line.substring(range.startCharacter); + } + } catch (StringIndexOutOfBoundsException e) { + log.info("Could not extract a slice from line \"{}\". The whole line is returned", line); } + return line; + } - private String getLineFromLineNumber(int line_num) { - String line = null; - try { - line = newContent.get(line_num); - } - catch (IndexOutOfBoundsException e) { - // If the line number returned by ChatGPT exceeds the actual number of lines, return the last line - int lastLine = newContent.size() - 1; - if (line_num > lastLine) { - line = newContent.get(lastLine); - } - else { - log.warn("Could not extract line #{} from the code", line_num); - } - } - return line; + private String getLineFromLineNumber(int line_num) { + String line = null; + try { + line = newContent.get(line_num); + } catch (IndexOutOfBoundsException e) { + // If the line number returned by ChatGPT exceeds the actual number of lines, return the last + // line + int lastLine = newContent.size() - 1; + if (line_num > lastLine) { + line = newContent.get(lastLine); + } else { + log.warn("Could not extract line #{} from the code", line_num); + } } + return line; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/comment/GerritCommentRange.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/comment/GerritCommentRange.java index 01c92c0..57eda9f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/comment/GerritCommentRange.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/comment/GerritCommentRange.java
@@ -4,41 +4,43 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.code.InlineCode; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritCodeRange; -import lombok.extern.slf4j.Slf4j; - +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatReplyItem; import java.util.HashMap; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritCommentRange { - private final HashMap<String, FileDiffProcessed> fileDiffsProcessed; + private final HashMap<String, FileDiffProcessed> fileDiffsProcessed; - public GerritCommentRange(GerritClient gerritClient, GerritChange change) { - fileDiffsProcessed = gerritClient.getFileDiffsProcessed(change); - } + public GerritCommentRange(GerritClient gerritClient, GerritChange change) { + fileDiffsProcessed = gerritClient.getFileDiffsProcessed(change); + } - public Optional<GerritCodeRange> getGerritCommentRange(AIChatReplyItem replyItem) { - Optional<GerritCodeRange> gerritCommentRange = Optional.empty(); - String filename = replyItem.getFilename(); - if (filename == null || filename.equals("/COMMIT_MSG")) { - return gerritCommentRange; - } - if (replyItem.getCodeSnippet() == null) { - log.info("CodeSnippet is null in reply '{}'.", replyItem); - return gerritCommentRange; - } - if (!fileDiffsProcessed.containsKey(filename)) { - log.info("Filename '{}' not found for reply '{}'.\nFileDiffsProcessed = {}", filename, replyItem, - fileDiffsProcessed); - return gerritCommentRange; - } - InlineCode inlineCode = new InlineCode(fileDiffsProcessed.get(filename)); - gerritCommentRange = inlineCode.findCommentRange(replyItem); - if (gerritCommentRange.isEmpty()) { - log.info("Inline code not found for reply {}", replyItem); - } - return gerritCommentRange; + public Optional<GerritCodeRange> getGerritCommentRange(AIChatReplyItem replyItem) { + Optional<GerritCodeRange> gerritCommentRange = Optional.empty(); + String filename = replyItem.getFilename(); + if (filename == null || filename.equals("/COMMIT_MSG")) { + return gerritCommentRange; } + if (replyItem.getCodeSnippet() == null) { + log.info("CodeSnippet is null in reply '{}'.", replyItem); + return gerritCommentRange; + } + if (!fileDiffsProcessed.containsKey(filename)) { + log.info( + "Filename '{}' not found for reply '{}'.\nFileDiffsProcessed = {}", + filename, + replyItem, + fileDiffsProcessed); + return gerritCommentRange; + } + InlineCode inlineCode = new InlineCode(fileDiffsProcessed.get(filename)); + gerritCommentRange = inlineCode.findCommentRange(replyItem); + if (gerritCommentRange.isEmpty()) { + log.info("Inline code not found for reply {}", replyItem); + } + return gerritCommentRange; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/diff/FileDiffProcessed.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/diff/FileDiffProcessed.java index 8fa74bc..0e560d5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/diff/FileDiffProcessed.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/patch/diff/FileDiffProcessed.java
@@ -1,133 +1,141 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritPatchSetFileDiff; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.code.CodeFinderDiff; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.diff.DiffContent; import com.googlesource.gerrit.plugins.aicodereview.settings.Settings; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; - import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; @Slf4j public class FileDiffProcessed { - private static final int MIN_RANDOM_PLACEHOLDER_VARIABLE_LENGTH = 1; + private static final int MIN_RANDOM_PLACEHOLDER_VARIABLE_LENGTH = 1; - private final Configuration config; - private final boolean isCommitMessage; - @Getter - private List<CodeFinderDiff> codeFinderDiffs; - @Getter - private List<String> newContent; - @Getter - private List<DiffContent> reviewDiffContent; - @Getter - private String randomPlaceholder; - private int lineNum; - private DiffContent diffContentItem; - private DiffContent reviewDiffContentItem; - private TreeMap<Integer, Integer> charToLineMapItem; + private final Configuration config; + private final boolean isCommitMessage; + @Getter private List<CodeFinderDiff> codeFinderDiffs; + @Getter private List<String> newContent; + @Getter private List<DiffContent> reviewDiffContent; + @Getter private String randomPlaceholder; + private int lineNum; + private DiffContent diffContentItem; + private DiffContent reviewDiffContentItem; + private TreeMap<Integer, Integer> charToLineMapItem; - public FileDiffProcessed(Configuration config, boolean isCommitMessage, - GerritPatchSetFileDiff gerritPatchSetFileDiff) { - this.config = config; - this.isCommitMessage = isCommitMessage; + public FileDiffProcessed( + Configuration config, + boolean isCommitMessage, + GerritPatchSetFileDiff gerritPatchSetFileDiff) { + this.config = config; + this.isCommitMessage = isCommitMessage; - updateContent(gerritPatchSetFileDiff); - updateRandomPlaceholder(gerritPatchSetFileDiff); - } + updateContent(gerritPatchSetFileDiff); + updateRandomPlaceholder(gerritPatchSetFileDiff); + } - private void updateContent(GerritPatchSetFileDiff gerritPatchSetFileDiff) { - newContent = new ArrayList<>() {{ + private void updateContent(GerritPatchSetFileDiff gerritPatchSetFileDiff) { + newContent = + new ArrayList<>() { + { add("DUMMY LINE #0"); - }}; - lineNum = 1; - reviewDiffContent = new ArrayList<>(); - codeFinderDiffs = new ArrayList<>(); - List<GerritPatchSetFileDiff.Content> patchSetDiffContent = gerritPatchSetFileDiff.getContent(); - // Iterate over the items of the diff content - for (GerritPatchSetFileDiff.Content patchSetContentItem : patchSetDiffContent) { - diffContentItem = new DiffContent(); - reviewDiffContentItem = new DiffContent(); - charToLineMapItem = new TreeMap<>(); - // Iterate over the fields `a`, `b` and `ab` of each diff content - for (Field patchSetDiffField : GerritPatchSetFileDiff.Content.class.getDeclaredFields()) { - processFileDiffItem(patchSetDiffField, patchSetContentItem); - } - reviewDiffContent.add(reviewDiffContentItem); - codeFinderDiffs.add(new CodeFinderDiff(diffContentItem, charToLineMapItem)); - } + } + }; + lineNum = 1; + reviewDiffContent = new ArrayList<>(); + codeFinderDiffs = new ArrayList<>(); + List<GerritPatchSetFileDiff.Content> patchSetDiffContent = gerritPatchSetFileDiff.getContent(); + // Iterate over the items of the diff content + for (GerritPatchSetFileDiff.Content patchSetContentItem : patchSetDiffContent) { + diffContentItem = new DiffContent(); + reviewDiffContentItem = new DiffContent(); + charToLineMapItem = new TreeMap<>(); + // Iterate over the fields `a`, `b` and `ab` of each diff content + for (Field patchSetDiffField : GerritPatchSetFileDiff.Content.class.getDeclaredFields()) { + processFileDiffItem(patchSetDiffField, patchSetContentItem); + } + reviewDiffContent.add(reviewDiffContentItem); + codeFinderDiffs.add(new CodeFinderDiff(diffContentItem, charToLineMapItem)); + } + } + + private void updateRandomPlaceholder(GerritPatchSetFileDiff gerritPatchSetFileDiff) { + int placeholderVariableLength = MIN_RANDOM_PLACEHOLDER_VARIABLE_LENGTH; + do { + randomPlaceholder = '#' + RandomStringUtils.random(placeholderVariableLength, true, false); + placeholderVariableLength++; + } while (gerritPatchSetFileDiff.toString().contains(randomPlaceholder)); + } + + private void filterCommitMessageContent(List<String> fieldValue) { + fieldValue.removeIf( + s -> + s.isEmpty() + || Settings.COMMIT_MESSAGE_FILTER_OUT_PREFIXES.values().stream() + .anyMatch(s::startsWith)); + } + + private void updateCodeEntities(Field diffField, List<String> diffLines) + throws IllegalAccessException { + String diffType = diffField.getName(); + String content = joinWithNewLine(diffLines); + diffField.set(diffContentItem, content); + // If the lines modified in the PatchSet are not deleted, they are utilized to populate + // newContent and + // charToLineMapItem + if (diffType.contains("b")) { + int diffCharPointer = -1; + for (String diffLine : diffLines) { + // Increase of 1 to take into account of the newline character + diffCharPointer++; + charToLineMapItem.put(diffCharPointer, lineNum); + diffCharPointer += diffLine.length(); + lineNum++; + } + // Add the last line to charToLineMapItem + charToLineMapItem.put(diffCharPointer + 1, lineNum); + newContent.addAll(diffLines); + } + // If the lines modified in the PatchSet are deleted, they are mapped in charToLineMapItem to + // current lineNum + else { + int startingPosition = charToLineMapItem.isEmpty() ? 0 : content.length(); + charToLineMapItem.put(startingPosition, lineNum); } - private void updateRandomPlaceholder(GerritPatchSetFileDiff gerritPatchSetFileDiff) { - int placeholderVariableLength = MIN_RANDOM_PLACEHOLDER_VARIABLE_LENGTH; - do { - randomPlaceholder = '#' + RandomStringUtils.random(placeholderVariableLength, true, false); - placeholderVariableLength++; - } - while (gerritPatchSetFileDiff.toString().contains(randomPlaceholder)); + if (config.getGptFullFileReview() || !diffType.equals("ab")) { + // Store the new field's value in the diff content for the Patch Set review + // `reviewDiffContentItem` + diffField.set(reviewDiffContentItem, content); } + } - private void filterCommitMessageContent(List<String> fieldValue) { - fieldValue.removeIf(s -> - s.isEmpty() || Settings.COMMIT_MESSAGE_FILTER_OUT_PREFIXES.values().stream().anyMatch(s::startsWith)); + private void processFileDiffItem( + Field patchSetDiffField, GerritPatchSetFileDiff.Content contentItem) { + try { + // Get the `a`, `b` or `ab` field's value from the Patch Set diff content + @SuppressWarnings("unchecked") + List<String> diffLines = (List<String>) patchSetDiffField.get(contentItem); + if (diffLines == null) { + return; + } + if (isCommitMessage) { + filterCommitMessageContent(diffLines); + } + // Get the corresponding `a`, `b` or `ab` field from the DiffContent class + Field diffField = DiffContent.class.getDeclaredField(patchSetDiffField.getName()); + updateCodeEntities(diffField, diffLines); + + } catch (IllegalAccessException | NoSuchFieldException e) { + log.error( + "Error while processing file difference (diff type: {})", patchSetDiffField.getName(), e); } - - private void updateCodeEntities(Field diffField, List<String> diffLines) throws IllegalAccessException { - String diffType = diffField.getName(); - String content = joinWithNewLine(diffLines); - diffField.set(diffContentItem, content); - // If the lines modified in the PatchSet are not deleted, they are utilized to populate newContent and - // charToLineMapItem - if (diffType.contains("b")) { - int diffCharPointer = -1; - for (String diffLine : diffLines) { - // Increase of 1 to take into account of the newline character - diffCharPointer++; - charToLineMapItem.put(diffCharPointer, lineNum); - diffCharPointer += diffLine.length(); - lineNum++; - } - // Add the last line to charToLineMapItem - charToLineMapItem.put(diffCharPointer +1, lineNum); - newContent.addAll(diffLines); - } - // If the lines modified in the PatchSet are deleted, they are mapped in charToLineMapItem to current lineNum - else { - int startingPosition = charToLineMapItem.isEmpty() ? 0 : content.length(); - charToLineMapItem.put(startingPosition, lineNum); - } - - if (config.getGptFullFileReview() || !diffType.equals("ab")) { - // Store the new field's value in the diff content for the Patch Set review `reviewDiffContentItem` - diffField.set(reviewDiffContentItem, content); - } - } - - private void processFileDiffItem(Field patchSetDiffField, GerritPatchSetFileDiff.Content contentItem) { - try { - // Get the `a`, `b` or `ab` field's value from the Patch Set diff content - @SuppressWarnings("unchecked") - List<String> diffLines = (List<String>) patchSetDiffField.get(contentItem); - if (diffLines == null) { - return; - } - if (isCommitMessage) { - filterCommitMessageContent(diffLines); - } - // Get the corresponding `a`, `b` or `ab` field from the DiffContent class - Field diffField = DiffContent.class.getDeclaredField(patchSetDiffField.getName()); - updateCodeEntities(diffField, diffLines); - - } catch (IllegalAccessException | NoSuchFieldException e) { - log.error("Error while processing file difference (diff type: {})", patchSetDiffField.getName(), e); - } - } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatComment.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatComment.java index be5ef0b..8b4791f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatComment.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatComment.java
@@ -10,29 +10,29 @@ @Slf4j public class AIChatComment extends ClientBase { - protected ClientMessage commentMessage; + protected ClientMessage commentMessage; - private final ChangeSetData changeSetData; - private final Localizer localizer; + private final ChangeSetData changeSetData; + private final Localizer localizer; - public AIChatComment(Configuration config, ChangeSetData changeSetData, Localizer localizer) { - super(config); - this.changeSetData = changeSetData; - this.localizer = localizer; + public AIChatComment(Configuration config, ChangeSetData changeSetData, Localizer localizer) { + super(config); + this.changeSetData = changeSetData; + this.localizer = localizer; + } + + protected String getCleanedMessage(GerritComment commentProperty) { + commentMessage = + new ClientMessage(config, changeSetData, commentProperty.getMessage(), localizer); + if (isFromAssistant(commentProperty)) { + commentMessage.removeDebugCodeBlocksReview().removeDebugCodeBlocksDynamicSettings(); + } else { + commentMessage.removeMentions().parseRemoveCommands(); } + return commentMessage.removeHeadings().getMessage(); + } - protected String getCleanedMessage(GerritComment commentProperty) { - commentMessage = new ClientMessage(config, changeSetData, commentProperty.getMessage(), localizer); - if (isFromAssistant(commentProperty)) { - commentMessage.removeDebugCodeBlocksReview().removeDebugCodeBlocksDynamicSettings(); - } - else { - commentMessage.removeMentions().parseRemoveCommands(); - } - return commentMessage.removeHeadings().getMessage(); - } - - protected boolean isFromAssistant(GerritComment commentProperty) { - return commentProperty.getAuthor().getAccountId() == changeSetData.getGptAccountId(); - } + protected boolean isFromAssistant(GerritComment commentProperty) { + return commentProperty.getAuthor().getAccountId() == changeSetData.getGptAccountId(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPrompt.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPrompt.java index 928e385..66bcccf 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPrompt.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPrompt.java
@@ -1,5 +1,7 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.prompt.ChatAIDataPrompt; import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; @@ -7,37 +9,29 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatMessageItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatDataPrompt { - private final ChatAIDataPrompt aiChatDataPromptHandler; + private final ChatAIDataPrompt aiChatDataPromptHandler; - public AIChatDataPrompt( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - GerritClientData gerritClientData, - Localizer localizer - ) { - aiChatDataPromptHandler = AIChatPromptFactory.getChatGptDataPrompt( - config, - changeSetData, - change, - gerritClientData, - localizer - ); - } + public AIChatDataPrompt( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + GerritClientData gerritClientData, + Localizer localizer) { + aiChatDataPromptHandler = + AIChatPromptFactory.getChatGptDataPrompt( + config, changeSetData, change, gerritClientData, localizer); + } - public String buildPrompt() { - for (int i = 0; i < aiChatDataPromptHandler.getCommentProperties().size(); i++) { - aiChatDataPromptHandler.addMessageItem(i); - } - List<AIChatMessageItem> messageItems = aiChatDataPromptHandler.getMessageItems(); - return messageItems.isEmpty() ? "" : getGson().toJson(messageItems); + public String buildPrompt() { + for (int i = 0; i < aiChatDataPromptHandler.getCommentProperties().size(); i++) { + aiChatDataPromptHandler.addMessageItem(i); } + List<AIChatMessageItem> messageItems = aiChatDataPromptHandler.getMessageItems(); + return messageItems.isEmpty() ? "" : getGson().toJson(messageItems); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptBase.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptBase.java index 6ef3bf2..8edf813 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptBase.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptBase.java
@@ -5,67 +5,64 @@ import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.code.InlineCode; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatMessageItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.CommentData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class AIChatDataPromptBase implements ChatAIDataPrompt { - protected final GerritClientData gerritClientData; - protected final HashMap<String, FileDiffProcessed> fileDiffsProcessed; - protected final CommentData commentData; - @Getter - protected final List<AIChatMessageItem> messageItems; + protected final GerritClientData gerritClientData; + protected final HashMap<String, FileDiffProcessed> fileDiffsProcessed; + protected final CommentData commentData; + @Getter protected final List<AIChatMessageItem> messageItems; - protected AIChatHistory aiChatHistory; - @Getter - protected List<GerritComment> commentProperties; + protected AIChatHistory aiChatHistory; + @Getter protected List<GerritComment> commentProperties; - public AIChatDataPromptBase( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - this.gerritClientData = gerritClientData; - fileDiffsProcessed = gerritClientData.getFileDiffsProcessed(); - commentData = gerritClientData.getCommentData(); - aiChatHistory = new AIChatHistory(config, changeSetData, gerritClientData, localizer); - messageItems = new ArrayList<>(); - } + public AIChatDataPromptBase( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + this.gerritClientData = gerritClientData; + fileDiffsProcessed = gerritClientData.getFileDiffsProcessed(); + commentData = gerritClientData.getCommentData(); + aiChatHistory = new AIChatHistory(config, changeSetData, gerritClientData, localizer); + messageItems = new ArrayList<>(); + } - public abstract void addMessageItem(int i); + public abstract void addMessageItem(int i); - protected AIChatMessageItem getMessageItem(int i) { - AIChatMessageItem messageItem = new AIChatMessageItem(); - GerritComment commentProperty = commentProperties.get(i); - if (commentProperty.getLine() != null || commentProperty.getRange() != null) { - String filename = commentProperty.getFilename(); - FileDiffProcessed fileDiffProcessed = fileDiffsProcessed.get(filename); - if (fileDiffProcessed == null) { - return messageItem; - } - InlineCode inlineCode = new InlineCode(fileDiffProcessed); - messageItem.setFilename(filename); - messageItem.setLineNumber(commentProperty.getLine()); - messageItem.setCodeSnippet(inlineCode.getInlineCode(commentProperty)); - } - + protected AIChatMessageItem getMessageItem(int i) { + AIChatMessageItem messageItem = new AIChatMessageItem(); + GerritComment commentProperty = commentProperties.get(i); + if (commentProperty.getLine() != null || commentProperty.getRange() != null) { + String filename = commentProperty.getFilename(); + FileDiffProcessed fileDiffProcessed = fileDiffsProcessed.get(filename); + if (fileDiffProcessed == null) { return messageItem; + } + InlineCode inlineCode = new InlineCode(fileDiffProcessed); + messageItem.setFilename(filename); + messageItem.setLineNumber(commentProperty.getLine()); + messageItem.setCodeSnippet(inlineCode.getInlineCode(commentProperty)); } - protected void setHistory(AIChatMessageItem messageItem, List<AIChatRequestMessage> messageHistory) { - if (!messageHistory.isEmpty()) { - messageItem.setHistory(messageHistory); - } + return messageItem; + } + + protected void setHistory( + AIChatMessageItem messageItem, List<AIChatRequestMessage> messageHistory) { + if (!messageHistory.isEmpty()) { + messageItem.setHistory(messageHistory); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptRequests.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptRequests.java index 708aa3a..afab2ad 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptRequests.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptRequests.java
@@ -1,54 +1,53 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.OPEN_AI_CHAT_ROLE_USER; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatMessageItem; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.OPEN_AI_CHAT_ROLE_USER; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatDataPromptRequests extends AIChatDataPromptBase { - protected AIChatMessageItem messageItem; - protected List<AIChatRequestMessage> messageHistory; + protected AIChatMessageItem messageItem; + protected List<AIChatRequestMessage> messageHistory; - public AIChatDataPromptRequests( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - super(config, changeSetData, gerritClientData, localizer); - commentProperties = commentData.getCommentProperties(); + public AIChatDataPromptRequests( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + super(config, changeSetData, gerritClientData, localizer); + commentProperties = commentData.getCommentProperties(); + } + + public void addMessageItem(int i) { + AIChatMessageItem messageItem = getMessageItem(i); + messageItem.setId(i); + messageItems.add(messageItem); + } + + protected AIChatMessageItem getMessageItem(int i) { + messageItem = super.getMessageItem(i); + messageHistory = aiChatHistory.retrieveHistory(commentProperties.get(i)); + AIChatRequestMessage request = extractLastUserMessageFromHistory(); + messageItem.setRequest(request.getContent()); + + return messageItem; + } + + private AIChatRequestMessage extractLastUserMessageFromHistory() { + for (int i = messageHistory.size() - 1; i >= 0; i--) { + if (OPEN_AI_CHAT_ROLE_USER.equals(messageHistory.get(i).getRole())) { + return messageHistory.remove(i); + } } - - public void addMessageItem(int i) { - AIChatMessageItem messageItem = getMessageItem(i); - messageItem.setId(i); - messageItems.add(messageItem); - } - - protected AIChatMessageItem getMessageItem(int i) { - messageItem = super.getMessageItem(i); - messageHistory = aiChatHistory.retrieveHistory(commentProperties.get(i)); - AIChatRequestMessage request = extractLastUserMessageFromHistory(); - messageItem.setRequest(request.getContent()); - - return messageItem; - } - - private AIChatRequestMessage extractLastUserMessageFromHistory() { - for (int i = messageHistory.size() - 1; i >= 0; i--) { - if (OPEN_AI_CHAT_ROLE_USER.equals(messageHistory.get(i).getRole())) { - return messageHistory.remove(i); - } - } - throw new RuntimeException("Error extracting request from message history: no user message found in " + - messageHistory); - } + throw new RuntimeException( + "Error extracting request from message history: no user message found in " + + messageHistory); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptReview.java index f49663f..76d257a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatDataPromptReview.java
@@ -7,36 +7,34 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatDataPromptReview extends AIChatDataPromptBase implements ChatAIDataPrompt { - public AIChatDataPromptReview( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - super(config, changeSetData, gerritClientData, localizer); - commentProperties = new ArrayList<>(commentData.getCommentMap().values()); - } + public AIChatDataPromptReview( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + super(config, changeSetData, gerritClientData, localizer); + commentProperties = new ArrayList<>(commentData.getCommentMap().values()); + } - public void addMessageItem(int i) { - AIChatMessageItem messageItem = getMessageItem(i); - if (messageItem.getHistory() != null) { - messageItems.add(messageItem); - } + public void addMessageItem(int i) { + AIChatMessageItem messageItem = getMessageItem(i); + if (messageItem.getHistory() != null) { + messageItems.add(messageItem); } + } - protected AIChatMessageItem getMessageItem(int i) { - AIChatMessageItem messageItem = super.getMessageItem(i); - List<AIChatRequestMessage> messageHistory = aiChatHistory.retrieveHistory(commentProperties.get(i), - true); - setHistory(messageItem, messageHistory); + protected AIChatMessageItem getMessageItem(int i) { + AIChatMessageItem messageItem = super.getMessageItem(i); + List<AIChatRequestMessage> messageHistory = + aiChatHistory.retrieveHistory(commentProperties.get(i), true); + setHistory(messageItem, messageHistory); - return messageItem; - } + return messageItem; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatGptPrompt.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatGptPrompt.java index d3d2a51..5def757 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatGptPrompt.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatGptPrompt.java
@@ -1,151 +1,170 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.INLINE_CODE_DELIMITER; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.SPACE; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithComma; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithSemicolon; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithSpace; + import com.google.gson.reflect.TypeToken; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Field; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatGptPrompt { - // Reply attributes - public static final String ATTRIBUTE_ID = "id"; - public static final String ATTRIBUTE_REPLY = "reply"; - public static final String ATTRIBUTE_SCORE = "score"; - public static final String ATTRIBUTE_REPEATED = "repeated"; - public static final String ATTRIBUTE_CONFLICTING = "conflicting"; - public static final String ATTRIBUTE_RELEVANCE = "relevance"; - public static final String ATTRIBUTE_CHANGE_ID = "changeId"; - public static final List<String> PATCH_SET_REVIEW_REPLY_ATTRIBUTES = new ArrayList<>(Arrays.asList( - ATTRIBUTE_REPLY, ATTRIBUTE_SCORE, ATTRIBUTE_REPEATED, ATTRIBUTE_CONFLICTING, ATTRIBUTE_RELEVANCE - )); - public static final List<String> REQUEST_REPLY_ATTRIBUTES = new ArrayList<>(Arrays.asList( - ATTRIBUTE_REPLY, ATTRIBUTE_ID, ATTRIBUTE_CHANGE_ID - )); + // Reply attributes + public static final String ATTRIBUTE_ID = "id"; + public static final String ATTRIBUTE_REPLY = "reply"; + public static final String ATTRIBUTE_SCORE = "score"; + public static final String ATTRIBUTE_REPEATED = "repeated"; + public static final String ATTRIBUTE_CONFLICTING = "conflicting"; + public static final String ATTRIBUTE_RELEVANCE = "relevance"; + public static final String ATTRIBUTE_CHANGE_ID = "changeId"; + public static final List<String> PATCH_SET_REVIEW_REPLY_ATTRIBUTES = + new ArrayList<>( + Arrays.asList( + ATTRIBUTE_REPLY, + ATTRIBUTE_SCORE, + ATTRIBUTE_REPEATED, + ATTRIBUTE_CONFLICTING, + ATTRIBUTE_RELEVANCE)); + public static final List<String> REQUEST_REPLY_ATTRIBUTES = + new ArrayList<>(Arrays.asList(ATTRIBUTE_REPLY, ATTRIBUTE_ID, ATTRIBUTE_CHANGE_ID)); - // Prompt constants loaded from JSON file - public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_DIRECTIVES; - public static String DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT; - public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_SPECS; - public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_INLINE; - public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_ENFORCE_RESPONSE_CHECK; - public static String DEFAULT_AI_CHAT_REQUEST_PROMPT_DIFF; - public static String DEFAULT_AI_CHAT_REQUEST_PROMPT_REQUESTS; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_COMMIT_MESSAGES; - public static String DEFAULT_AI_CHAT_RELEVANCE_RULES; - public static String DEFAULT_AI_CHAT_HOW_TO_FIND_COMMIT_MESSAGE; - public static Map<String, String> DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES; + // Prompt constants loaded from JSON file + public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_DIRECTIVES; + public static String DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT; + public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_SPECS; + public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_INLINE; + public static String DEFAULT_AI_CHAT_REPLIES_PROMPT_ENFORCE_RESPONSE_CHECK; + public static String DEFAULT_AI_CHAT_REQUEST_PROMPT_DIFF; + public static String DEFAULT_AI_CHAT_REQUEST_PROMPT_REQUESTS; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_COMMIT_MESSAGES; + public static String DEFAULT_AI_CHAT_RELEVANCE_RULES; + public static String DEFAULT_AI_CHAT_HOW_TO_FIND_COMMIT_MESSAGE; + public static Map<String, String> DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES; - protected final Configuration config; + protected final Configuration config; - @Setter - protected boolean isCommentEvent; + @Setter protected boolean isCommentEvent; - public AIChatGptPrompt(Configuration config) { - this(config, false); + public AIChatGptPrompt(Configuration config) { + this(config, false); + } + + public AIChatGptPrompt(Configuration config, boolean isCommentEvent) { + this.config = config; + this.isCommentEvent = isCommentEvent; + // Avoid repeated loading of prompt constants + if (DEFAULT_AI_CHAT_SYSTEM_PROMPT == null) { + loadDefaultPrompts("prompts"); } + } - public AIChatGptPrompt(Configuration config, boolean isCommentEvent) { - this.config = config; - this.isCommentEvent = isCommentEvent; - // Avoid repeated loading of prompt constants - if (DEFAULT_AI_CHAT_SYSTEM_PROMPT == null) { - loadDefaultPrompts("prompts"); - } - } - - public static String getCommentRequestPrompt(int commentPropertiesSize) { - return joinWithSpace(new ArrayList<>(List.of( + public static String getCommentRequestPrompt(int commentPropertiesSize) { + return joinWithSpace( + new ArrayList<>( + List.of( DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT, buildFieldSpecifications(REQUEST_REPLY_ATTRIBUTES), DEFAULT_AI_CHAT_REPLIES_PROMPT_INLINE, - String.format(DEFAULT_AI_CHAT_REPLIES_PROMPT_ENFORCE_RESPONSE_CHECK, commentPropertiesSize) - ))); - } + String.format( + DEFAULT_AI_CHAT_REPLIES_PROMPT_ENFORCE_RESPONSE_CHECK, + commentPropertiesSize)))); + } - public static String getReviewPromptCommitMessages() { - return String.format(DEFAULT_AI_CHAT_REVIEW_PROMPT_COMMIT_MESSAGES, DEFAULT_AI_CHAT_HOW_TO_FIND_COMMIT_MESSAGE); - } + public static String getReviewPromptCommitMessages() { + return String.format( + DEFAULT_AI_CHAT_REVIEW_PROMPT_COMMIT_MESSAGES, DEFAULT_AI_CHAT_HOW_TO_FIND_COMMIT_MESSAGE); + } - protected void loadDefaultPrompts(String promptFilename) { - String promptFile = String.format("config/%s.json", promptFilename); - Class<? extends AIChatGptPrompt> me = this.getClass(); - try (InputStreamReader reader = FileUtils.getInputStreamReader(promptFile)) { - Map<String, Object> values = getGson().fromJson(reader, new TypeToken<Map<String, Object>>(){}.getType()); - for (Map.Entry<String, Object> entry : values.entrySet()) { - try { - Field field = me.getField(entry.getKey()); - field.setAccessible(true); - field.set(null, entry.getValue()); - } catch (NoSuchFieldException | IllegalAccessException e) { - log.error("Error setting prompt '{}'", entry.getKey(), e); - throw new IOException(); - } - } - } catch (IOException e) { - throw new RuntimeException("Failed to load prompts", e); + protected void loadDefaultPrompts(String promptFilename) { + String promptFile = String.format("config/%s.json", promptFilename); + Class<? extends AIChatGptPrompt> me = this.getClass(); + try (InputStreamReader reader = FileUtils.getInputStreamReader(promptFile)) { + Map<String, Object> values = + getGson().fromJson(reader, new TypeToken<Map<String, Object>>() {}.getType()); + for (Map.Entry<String, Object> entry : values.entrySet()) { + try { + Field field = me.getField(entry.getKey()); + field.setAccessible(true); + field.set(null, entry.getValue()); + } catch (NoSuchFieldException | IllegalAccessException e) { + log.error("Error setting prompt '{}'", entry.getKey(), e); + throw new IOException(); } - // Keep the given order of attributes - DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES = new LinkedHashMap<>(DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES); + } + } catch (IOException e) { + throw new RuntimeException("Failed to load prompts", e); } + // Keep the given order of attributes + DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES = new LinkedHashMap<>(DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES); + } - protected static String buildFieldSpecifications(List<String> filterFields) { - Set<String> orderedFilterFields = new LinkedHashSet<>(filterFields); - Map<String, String> attributes = DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.entrySet().stream() - .filter(entry -> orderedFilterFields.contains(entry.getKey())) - .collect(Collectors.toMap( - entry -> INLINE_CODE_DELIMITER + entry.getKey() + INLINE_CODE_DELIMITER, - Map.Entry::getValue, - (oldValue, newValue) -> oldValue, - LinkedHashMap::new - )); - List<String> fieldDescription = attributes.entrySet().stream() - .map(entry -> entry.getKey() + SPACE + entry.getValue()) - .collect(Collectors.toList()); + protected static String buildFieldSpecifications(List<String> filterFields) { + Set<String> orderedFilterFields = new LinkedHashSet<>(filterFields); + Map<String, String> attributes = + DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.entrySet().stream() + .filter(entry -> orderedFilterFields.contains(entry.getKey())) + .collect( + Collectors.toMap( + entry -> INLINE_CODE_DELIMITER + entry.getKey() + INLINE_CODE_DELIMITER, + Map.Entry::getValue, + (oldValue, newValue) -> oldValue, + LinkedHashMap::new)); + List<String> fieldDescription = + attributes.entrySet().stream() + .map(entry -> entry.getKey() + SPACE + entry.getValue()) + .collect(Collectors.toList()); - return String.format(DEFAULT_AI_CHAT_REPLIES_PROMPT_SPECS, - joinWithComma(attributes.keySet()), - joinWithSemicolon(fieldDescription) - ); + return String.format( + DEFAULT_AI_CHAT_REPLIES_PROMPT_SPECS, + joinWithComma(attributes.keySet()), + joinWithSemicolon(fieldDescription)); + } + + public String getPatchSetReviewPrompt() { + List<String> attributes = new ArrayList<>(PATCH_SET_REVIEW_REPLY_ATTRIBUTES); + if (config.isVotingEnabled() || config.getFilterNegativeComments()) { + updateScoreDescription(); + } else { + attributes.remove(ATTRIBUTE_SCORE); } + updateRelevanceDescription(); + return buildFieldSpecifications(attributes) + SPACE + DEFAULT_AI_CHAT_REPLIES_PROMPT_INLINE; + } - public String getPatchSetReviewPrompt() { - List<String> attributes = new ArrayList<>(PATCH_SET_REVIEW_REPLY_ATTRIBUTES); - if (config.isVotingEnabled() || config.getFilterNegativeComments()) { - updateScoreDescription(); - } - else { - attributes.remove(ATTRIBUTE_SCORE); - } - updateRelevanceDescription(); - return buildFieldSpecifications(attributes) + SPACE + DEFAULT_AI_CHAT_REPLIES_PROMPT_INLINE; + private void updateScoreDescription() { + String scoreDescription = DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.get(ATTRIBUTE_SCORE); + if (scoreDescription.contains("%d")) { + scoreDescription = + String.format(scoreDescription, config.getVotingMinScore(), config.getVotingMaxScore()); + DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.put(ATTRIBUTE_SCORE, scoreDescription); } + } - private void updateScoreDescription() { - String scoreDescription = DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.get(ATTRIBUTE_SCORE); - if (scoreDescription.contains("%d")) { - scoreDescription = String.format(scoreDescription, config.getVotingMinScore(), config.getVotingMaxScore()); - DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.put(ATTRIBUTE_SCORE, scoreDescription); - } + private void updateRelevanceDescription() { + String relevanceDescription = DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.get(ATTRIBUTE_RELEVANCE); + if (relevanceDescription.contains("%s")) { + String defaultAIRelevanceRules = + config.getString(Configuration.KEY_AI_RELEVANCE_RULES, DEFAULT_AI_CHAT_RELEVANCE_RULES); + relevanceDescription = String.format(relevanceDescription, defaultAIRelevanceRules); + DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.put(ATTRIBUTE_RELEVANCE, relevanceDescription); } - - private void updateRelevanceDescription() { - String relevanceDescription = DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.get(ATTRIBUTE_RELEVANCE); - if (relevanceDescription.contains("%s")) { - String defaultAIRelevanceRules = config.getString(Configuration.KEY_AI_RELEVANCE_RULES, - DEFAULT_AI_CHAT_RELEVANCE_RULES); - relevanceDescription = String.format(relevanceDescription, defaultAIRelevanceRules); - DEFAULT_AI_CHAT_REPLIES_ATTRIBUTES.put(ATTRIBUTE_RELEVANCE, relevanceDescription); - } - } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatHistory.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatHistory.java index f48462c..e528346 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatHistory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatHistory.java
@@ -2,147 +2,159 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.CommentData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; import com.googlesource.gerrit.plugins.aicodereview.settings.Settings; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatHistory extends AIChatComment { - private final Set<String> messagesExcludedFromHistory; - private final HashMap<String, GerritComment> commentMap; - private final HashMap<String, GerritComment> patchSetCommentMap; - private final Set<String> patchSetCommentAdded; - private final List<GerritComment> patchSetComments; - private final int revisionBase; + private final Set<String> messagesExcludedFromHistory; + private final HashMap<String, GerritComment> commentMap; + private final HashMap<String, GerritComment> patchSetCommentMap; + private final Set<String> patchSetCommentAdded; + private final List<GerritComment> patchSetComments; + private final int revisionBase; - private boolean filterActive; + private boolean filterActive; - public AIChatHistory( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - super(config, changeSetData, localizer); - CommentData commentData = gerritClientData.getCommentData(); - messagesExcludedFromHistory = Set.of( - Settings.GERRIT_DEFAULT_MESSAGE_DONE, - localizer.getText("message.empty.review") - ); - commentMap = commentData.getCommentMap(); - patchSetCommentMap = commentData.getPatchSetCommentMap(); - patchSetComments = retrievePatchSetComments(gerritClientData); - revisionBase = gerritClientData.getOneBasedRevisionBase(); - patchSetCommentAdded = new HashSet<>(); + public AIChatHistory( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + super(config, changeSetData, localizer); + CommentData commentData = gerritClientData.getCommentData(); + messagesExcludedFromHistory = + Set.of(Settings.GERRIT_DEFAULT_MESSAGE_DONE, localizer.getText("message.empty.review")); + commentMap = commentData.getCommentMap(); + patchSetCommentMap = commentData.getPatchSetCommentMap(); + patchSetComments = retrievePatchSetComments(gerritClientData); + revisionBase = gerritClientData.getOneBasedRevisionBase(); + patchSetCommentAdded = new HashSet<>(); + } + + public List<AIChatRequestMessage> retrieveHistory( + GerritComment commentProperty, boolean filterActive) { + this.filterActive = filterActive; + if (commentProperty.isPatchSetComment()) { + return retrievePatchSetMessageHistory(); + } else { + return retrieveMessageHistory(commentProperty); } + } - public List<AIChatRequestMessage> retrieveHistory(GerritComment commentProperty, boolean filterActive) { - this.filterActive = filterActive; - if (commentProperty.isPatchSetComment()) { - return retrievePatchSetMessageHistory(); - } - else { - return retrieveMessageHistory(commentProperty); - } - } + public List<AIChatRequestMessage> retrieveHistory(GerritComment commentProperty) { + return retrieveHistory(commentProperty, false); + } - public List<AIChatRequestMessage> retrieveHistory(GerritComment commentProperty) { - return retrieveHistory(commentProperty, false); - } + private List<GerritComment> retrievePatchSetComments(GerritClientData gerritClientData) { + List<GerritComment> detailComments = gerritClientData.getDetailComments(); + // Normalize detailComments by setting the `update` field to match `date` + detailComments.forEach(record -> record.setUpdated(record.getDate())); + // Join the comments from patchSetCommentMap with detailComments + List<GerritComment> patchSetComments = + Stream.concat(patchSetCommentMap.values().stream(), detailComments.stream()) + .collect(Collectors.toList()); + sortPatchSetComments(patchSetComments); + log.debug("Patch Set Comments sorted by `update` datetime: {}", patchSetComments); - private List<GerritComment> retrievePatchSetComments(GerritClientData gerritClientData) { - List<GerritComment> detailComments = gerritClientData.getDetailComments(); - // Normalize detailComments by setting the `update` field to match `date` - detailComments.forEach(record -> record.setUpdated(record.getDate())); - // Join the comments from patchSetCommentMap with detailComments - List<GerritComment> patchSetComments = Stream.concat(patchSetCommentMap.values().stream(), - detailComments.stream()) - .collect(Collectors.toList()); - sortPatchSetComments(patchSetComments); - log.debug("Patch Set Comments sorted by `update` datetime: {}", patchSetComments); + return patchSetComments; + } - return patchSetComments; - } + private void sortPatchSetComments(List<GerritComment> patchSetComments) { + Comparator<GerritComment> byDateUpdated = + (GerritComment o1, GerritComment o2) -> { + String dateTime1 = o1.getUpdated(); + String dateTime2 = o2.getUpdated(); + if (dateTime1 == null && dateTime2 == null) return 0; + if (dateTime1 == null) return 1; + if (dateTime2 == null) return -1; - private void sortPatchSetComments(List<GerritComment> patchSetComments) { - Comparator<GerritComment> byDateUpdated = (GerritComment o1, GerritComment o2) -> { - String dateTime1 = o1.getUpdated(); - String dateTime2 = o2.getUpdated(); - if (dateTime1 == null && dateTime2 == null) return 0; - if (dateTime1 == null) return 1; - if (dateTime2 == null) return -1; - - return dateTime1.compareTo(dateTime2); + return dateTime1.compareTo(dateTime2); }; - patchSetComments.sort(byDateUpdated); - } + patchSetComments.sort(byDateUpdated); + } - private String getRoleFromComment(GerritComment currentComment) { - return isFromAssistant(currentComment) ? Settings.OPEN_AI_CHAT_ROLE_ASSISTANT : Settings.OPEN_AI_CHAT_ROLE_USER; - } + private String getRoleFromComment(GerritComment currentComment) { + return isFromAssistant(currentComment) + ? Settings.OPEN_AI_CHAT_ROLE_ASSISTANT + : Settings.OPEN_AI_CHAT_ROLE_USER; + } - private List<AIChatRequestMessage> retrieveMessageHistory(GerritComment currentComment) { - List<AIChatRequestMessage> messageHistory = new ArrayList<>(); - while (currentComment != null) { - addMessageToHistory(messageHistory, currentComment); - currentComment = commentMap.get(currentComment.getInReplyTo()); + private List<AIChatRequestMessage> retrieveMessageHistory(GerritComment currentComment) { + List<AIChatRequestMessage> messageHistory = new ArrayList<>(); + while (currentComment != null) { + addMessageToHistory(messageHistory, currentComment); + currentComment = commentMap.get(currentComment.getInReplyTo()); + } + // Reverse the history sequence so that the oldest message appears first and the newest message + // is last + Collections.reverse(messageHistory); + + return messageHistory; + } + + private List<AIChatRequestMessage> retrievePatchSetMessageHistory() { + List<AIChatRequestMessage> messageHistory = new ArrayList<>(); + for (GerritComment patchSetComment : patchSetComments) { + if (patchSetComment.isAutogenerated()) { + continue; + } + if (!isFromAssistant(patchSetComment)) { + GerritComment patchSetLevelMessage = patchSetCommentMap.get(patchSetComment.getId()); + if (patchSetLevelMessage != null) { + patchSetComment = patchSetLevelMessage; } - // Reverse the history sequence so that the oldest message appears first and the newest message is last - Collections.reverse(messageHistory); - - return messageHistory; + } + addMessageToHistory(messageHistory, patchSetComment); } + return messageHistory; + } - private List<AIChatRequestMessage> retrievePatchSetMessageHistory() { - List<AIChatRequestMessage> messageHistory = new ArrayList<>(); - for (GerritComment patchSetComment : patchSetComments) { - if (patchSetComment.isAutogenerated()) { - continue; - } - if (!isFromAssistant(patchSetComment)) { - GerritComment patchSetLevelMessage = patchSetCommentMap.get(patchSetComment.getId()); - if (patchSetLevelMessage != null) { - patchSetComment = patchSetLevelMessage; - } - } - addMessageToHistory(messageHistory, patchSetComment); - } - return messageHistory; + private boolean isInactiveComment(GerritComment comment) { + return config.getIgnoreResolvedAIChatComments() + && isFromAssistant(comment) + && comment.isResolved() + || config.getIgnoreOutdatedInlineComments() + && comment.getOneBasedPatchSet() != revisionBase + && !comment.isPatchSetComment(); + } + + private void addMessageToHistory( + List<AIChatRequestMessage> messageHistory, GerritComment comment) { + String messageContent = getCleanedMessage(comment); + boolean shouldNotProcessComment = + messageContent.isEmpty() + || messagesExcludedFromHistory.contains(messageContent) + || patchSetCommentAdded.contains(messageContent) + || filterActive && isInactiveComment(comment); + + if (shouldNotProcessComment && !commentMessage.isContainingHistoryCommand()) { + return; } - - private boolean isInactiveComment(GerritComment comment) { - return config.getIgnoreResolvedAIChatComments() && isFromAssistant(comment) && comment.isResolved() || - config.getIgnoreOutdatedInlineComments() && comment.getOneBasedPatchSet() != revisionBase && - !comment.isPatchSetComment(); + patchSetCommentAdded.add(messageContent); + if (commentMessage.isContainingHistoryCommand()) { + commentMessage.processHistoryCommand(); + return; } - - private void addMessageToHistory(List<AIChatRequestMessage> messageHistory, GerritComment comment) { - String messageContent = getCleanedMessage(comment); - boolean shouldNotProcessComment = messageContent.isEmpty() || - messagesExcludedFromHistory.contains(messageContent) || - patchSetCommentAdded.contains(messageContent) || - filterActive && isInactiveComment(comment); - - if (shouldNotProcessComment && !commentMessage.isContainingHistoryCommand()) { - return; - } - patchSetCommentAdded.add(messageContent); - if (commentMessage.isContainingHistoryCommand()) { - commentMessage.processHistoryCommand(); - return; - } - AIChatRequestMessage message = AIChatRequestMessage.builder() - .role(getRoleFromComment(comment)) - .content(messageContent) - .build(); - messageHistory.add(message); - } + AIChatRequestMessage message = + AIChatRequestMessage.builder() + .role(getRoleFromComment(comment)) + .content(messageContent) + .build(); + messageHistory.add(message); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatPromptFactory.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatPromptFactory.java index 1d294a0..af1d7e0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatPromptFactory.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/AIChatPromptFactory.java
@@ -17,38 +17,36 @@ @Slf4j public class AIChatPromptFactory { - public static ChatGptPromptStateful getAIChatPromptStateful( - Configuration config, - ChangeSetData changeSetData, - GerritChange change - ) { - if (change.getIsCommentEvent()) { - log.info("AIChatPromptFactory: Returned AIChatGptPromptStatefulRequests"); - return new AIChatGptPromptStatefulRequests(config, changeSetData, change); - } else { - log.info("AIChatPromptFactory: Returned AIChatGptPromptStatefulReview"); - return new AIChatGptPromptStatefulReview(config, changeSetData, change); - } + public static ChatGptPromptStateful getAIChatPromptStateful( + Configuration config, ChangeSetData changeSetData, GerritChange change) { + if (change.getIsCommentEvent()) { + log.info("AIChatPromptFactory: Returned AIChatGptPromptStatefulRequests"); + return new AIChatGptPromptStatefulRequests(config, changeSetData, change); + } else { + log.info("AIChatPromptFactory: Returned AIChatGptPromptStatefulReview"); + return new AIChatGptPromptStatefulReview(config, changeSetData, change); } + } - public static ChatAIDataPrompt getChatGptDataPrompt( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - GerritClientData gerritClientData, - Localizer localizer - ) { - if (change.getIsCommentEvent()) { - if ((config.getAIMode() == Settings.Modes.stateless)) { - log.info("AIChatPromptFactory: Returned AIChatDataPromptRequestsStateless"); - return new AIChatDataPromptRequestsStateless(config, changeSetData, gerritClientData, localizer); - } else { - log.info("AIChatPromptFactory: Returned AIChatDataPromptRequestsStateful"); - return new AIChatDataPromptRequestsStateful(config, changeSetData, gerritClientData, localizer); - } - } else { - log.info("AIChatPromptFactory: Returned AIChatDataPromptReview"); - return new AIChatDataPromptReview(config, changeSetData, gerritClientData, localizer); - } + public static ChatAIDataPrompt getChatGptDataPrompt( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + GerritClientData gerritClientData, + Localizer localizer) { + if (change.getIsCommentEvent()) { + if ((config.getAIMode() == Settings.Modes.stateless)) { + log.info("AIChatPromptFactory: Returned AIChatDataPromptRequestsStateless"); + return new AIChatDataPromptRequestsStateless( + config, changeSetData, gerritClientData, localizer); + } else { + log.info("AIChatPromptFactory: Returned AIChatDataPromptRequestsStateful"); + return new AIChatDataPromptRequestsStateful( + config, changeSetData, gerritClientData, localizer); + } + } else { + log.info("AIChatPromptFactory: Returned AIChatDataPromptReview"); + return new AIChatDataPromptReview(config, changeSetData, gerritClientData, localizer); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/Directives.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/Directives.java index ea1a6ed..5c0a546 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/Directives.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/Directives.java
@@ -3,20 +3,20 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; public class Directives { - private final ChangeSetData changeSetData; - private String directive; + private final ChangeSetData changeSetData; + private String directive; - public Directives(ChangeSetData changeSetData) { - this.changeSetData = changeSetData; - } + public Directives(ChangeSetData changeSetData) { + this.changeSetData = changeSetData; + } - public void addDirective(String directive) { - this.directive = directive.trim(); - } + public void addDirective(String directive) { + this.directive = directive.trim(); + } - public void copyDirectiveToSettings() { - if (directive != null && !directive.isEmpty()) { - changeSetData.getDirectives().add(directive); - } + public void copyDirectiveToSettings() { + if (directive != null && !directive.isEmpty()) { + changeSetData.getDirectives().add(directive); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/MessageSanitizer.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/MessageSanitizer.java index 7bf49d2..24e56d4 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/MessageSanitizer.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/client/prompt/MessageSanitizer.java
@@ -1,44 +1,59 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.StringUtils.backslashEachChar; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.CODE_DELIMITER; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.CODE_DELIMITER_BEGIN; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.CODE_DELIMITER_END; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.INLINE_CODE_DELIMITER; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.parseOutOfDelimiters; + import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; - public class MessageSanitizer { - private static final Pattern SANITIZE_BOLD_REGEX = Pattern.compile("(\\*{1,2}|(?<!\\w)_{1,2})(.+?)\\1", - Pattern.DOTALL); - private static final Pattern SANITIZE_NUM_REGEX = Pattern.compile("^(\\s*)(#+)(?=\\s)", Pattern.MULTILINE); + private static final Pattern SANITIZE_BOLD_REGEX = + Pattern.compile("(\\*{1,2}|(?<!\\w)_{1,2})(.+?)\\1", Pattern.DOTALL); + private static final Pattern SANITIZE_NUM_REGEX = + Pattern.compile("^(\\s*)(#+)(?=\\s)", Pattern.MULTILINE); - public static String sanitizeAIChatMessage(String message) { - // Sanitize code blocks (delimited by CODE_DELIMITER) by stripping out the language for syntax highlighting and - // ensuring that is preceded by two "\n" chars. Additionally, sanitize the content outside these blocks. - return parseOutOfDelimiters(message, "\\s*" + CODE_DELIMITER + "\\w*\\s*", - MessageSanitizer::sanitizeOutsideInlineCodeBlocks, CODE_DELIMITER_BEGIN, CODE_DELIMITER_END); + public static String sanitizeAIChatMessage(String message) { + // Sanitize code blocks (delimited by CODE_DELIMITER) by stripping out the language for syntax + // highlighting and + // ensuring that is preceded by two "\n" chars. Additionally, sanitize the content outside these + // blocks. + return parseOutOfDelimiters( + message, + "\\s*" + CODE_DELIMITER + "\\w*\\s*", + MessageSanitizer::sanitizeOutsideInlineCodeBlocks, + CODE_DELIMITER_BEGIN, + CODE_DELIMITER_END); + } + + private static String sanitizeOutsideInlineCodeBlocks(String message) { + // Sanitize the content outside the inline code blocks (delimited by INLINE_CODE_DELIMITER). + return parseOutOfDelimiters( + message, INLINE_CODE_DELIMITER, MessageSanitizer::sanitizeGerritComment); + } + + private static String sanitizeGerritComment(String message) { + // Sanitize sequences of asterisks ("*") and underscores ("_") that would be incorrectly + // interpreted as + // delimiters of Italic and Bold text. + Matcher boldSanitizeMatcher = SANITIZE_BOLD_REGEX.matcher(message); + StringBuilder result = new StringBuilder(); + while (boldSanitizeMatcher.find()) { + String slashedDelimiter = backslashEachChar(boldSanitizeMatcher.group(1)); + boldSanitizeMatcher.appendReplacement( + result, slashedDelimiter + boldSanitizeMatcher.group(2) + slashedDelimiter); } + boldSanitizeMatcher.appendTail(result); + message = result.toString(); - private static String sanitizeOutsideInlineCodeBlocks(String message) { - // Sanitize the content outside the inline code blocks (delimited by INLINE_CODE_DELIMITER). - return parseOutOfDelimiters(message, INLINE_CODE_DELIMITER, MessageSanitizer::sanitizeGerritComment); - } + // Sanitize sequences of number signs ("#") that would be incorrectly interpreted as header + // prefixes. + Matcher numSanitizeMatcher = SANITIZE_NUM_REGEX.matcher(message); + message = numSanitizeMatcher.replaceAll("$1\\\\$2"); - private static String sanitizeGerritComment(String message) { - // Sanitize sequences of asterisks ("*") and underscores ("_") that would be incorrectly interpreted as - // delimiters of Italic and Bold text. - Matcher boldSanitizeMatcher = SANITIZE_BOLD_REGEX.matcher(message); - StringBuilder result = new StringBuilder(); - while (boldSanitizeMatcher.find()) { - String slashedDelimiter = backslashEachChar(boldSanitizeMatcher.group(1)); - boldSanitizeMatcher.appendReplacement(result, slashedDelimiter + boldSanitizeMatcher.group(2) + - slashedDelimiter); - } - boldSanitizeMatcher.appendTail(result); - message = result.toString(); - - // Sanitize sequences of number signs ("#") that would be incorrectly interpreted as header prefixes. - Matcher numSanitizeMatcher = SANITIZE_NUM_REGEX.matcher(message); - message = numSanitizeMatcher.replaceAll("$1\\\\$2"); - - return message; - } + return message; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritCodeRange.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritCodeRange.java index 6917006..d17f15b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritCodeRange.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritCodeRange.java
@@ -7,12 +7,15 @@ @Data @Builder(toBuilder = true) public class GerritCodeRange { - @SerializedName("start_line") - public int startLine; - @SerializedName("end_line") - public int endLine; - @SerializedName("start_character") - public int startCharacter; - @SerializedName("end_character") - public int endCharacter; + @SerializedName("start_line") + public int startLine; + + @SerializedName("end_line") + public int endLine; + + @SerializedName("start_character") + public int startCharacter; + + @SerializedName("end_character") + public int endCharacter; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritComment.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritComment.java index defea13..b1e29b9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritComment.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritComment.java
@@ -6,52 +6,63 @@ @Data public class GerritComment { - private Author author; - @SerializedName("change_message_id") - private String changeMessageId; - private Boolean unresolved; - @SerializedName("patch_set") - private Integer patchSet; - private String id; - private String tag; - private Integer line; - private GerritCodeRange range; - @SerializedName("in_reply_to") - private String inReplyTo; - private String updated; - // Field `date` is used by messages from PatchSet details - private String date; - private String message; - @SerializedName("commit_id") - private String commitId; - // Metadata field that is set to the commented filename - private String filename; + private Author author; - @Data - public static class Author { - @SerializedName("_account_id") - private int accountId; - private String name; - @SerializedName("display_name") - private String displayName; - private String email; - private String username; - } + @SerializedName("change_message_id") + private String changeMessageId; - public boolean isAutogenerated() { - return tag != null && tag.startsWith(Settings.GERRIT_AUTOGENERATED_PREFIX); - } + private Boolean unresolved; - public boolean isPatchSetComment() { - return filename != null && filename.equals(Settings.GERRIT_PATCH_SET_FILENAME); - } + @SerializedName("patch_set") + private Integer patchSet; - public boolean isResolved() { - // unresolved is null for the messages from Patch Set details - return unresolved != null && !unresolved; - } + private String id; + private String tag; + private Integer line; + private GerritCodeRange range; - public int getOneBasedPatchSet() { - return patchSet == null ? 1 : Math.max(patchSet, 1); - } + @SerializedName("in_reply_to") + private String inReplyTo; + + private String updated; + // Field `date` is used by messages from PatchSet details + private String date; + private String message; + + @SerializedName("commit_id") + private String commitId; + + // Metadata field that is set to the commented filename + private String filename; + + @Data + public static class Author { + @SerializedName("_account_id") + private int accountId; + + private String name; + + @SerializedName("display_name") + private String displayName; + + private String email; + private String username; + } + + public boolean isAutogenerated() { + return tag != null && tag.startsWith(Settings.GERRIT_AUTOGENERATED_PREFIX); + } + + public boolean isPatchSetComment() { + return filename != null && filename.equals(Settings.GERRIT_PATCH_SET_FILENAME); + } + + public boolean isResolved() { + // unresolved is null for the messages from Patch Set details + return unresolved != null && !unresolved; + } + + public int getOneBasedPatchSet() { + return patchSet == null ? 1 : Math.max(patchSet, 1); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritFileDiff.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritFileDiff.java index 4daf319..35cc11c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritFileDiff.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritFileDiff.java
@@ -5,15 +5,17 @@ @Data public abstract class GerritFileDiff { - @SerializedName("meta_a") - protected Meta metaA; - @SerializedName("meta_b") - protected Meta metaB; + @SerializedName("meta_a") + protected Meta metaA; - @Data - public static class Meta { - String name; - @SerializedName("content_type") - String contentType; - } + @SerializedName("meta_b") + protected Meta metaB; + + @Data + public static class Meta { + String name; + + @SerializedName("content_type") + String contentType; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetDetail.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetDetail.java index d33a02d..7a4de23 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetDetail.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetDetail.java
@@ -1,36 +1,39 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit; import com.google.gson.annotations.SerializedName; +import java.util.List; import lombok.Data; -import java.util.List; - -// TODO remove once migration to GerritApi is finished and com.google.gerrit.extensions.common.ChangeInfo is used +// TODO remove once migration to GerritApi is finished and +// com.google.gerrit.extensions.common.ChangeInfo is used @Data public class GerritPatchSetDetail { - private Labels labels; - private List<GerritComment> messages; - @SerializedName("work_in_progress") - private Boolean workInProgress; + private Labels labels; + private List<GerritComment> messages; - @Data - public static class Labels { - @SerializedName("Code-Review") - private CodeReview codeReview; - } + @SerializedName("work_in_progress") + private Boolean workInProgress; - @Data - public static class CodeReview { - private List<Permission> all; - } + @Data + public static class Labels { + @SerializedName("Code-Review") + private CodeReview codeReview; + } - @Data - public static class Permission { - private Integer value; - private String date; - @SerializedName("permitted_voting_range") - private GerritPermittedVotingRange permittedVotingRange; - @SerializedName("_account_id") - private int accountId; - } + @Data + public static class CodeReview { + private List<Permission> all; + } + + @Data + public static class Permission { + private Integer value; + private String date; + + @SerializedName("permitted_voting_range") + private GerritPermittedVotingRange permittedVotingRange; + + @SerializedName("_account_id") + private int accountId; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetFileDiff.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetFileDiff.java index 27d4873..a20ea6b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetFileDiff.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPatchSetFileDiff.java
@@ -1,19 +1,18 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - @EqualsAndHashCode(callSuper = true) @Data public class GerritPatchSetFileDiff extends GerritFileDiff { - private List<Content> content; + private List<Content> content; - @Data - public static class Content { - public List<String> a; - public List<String> b; - public List<String> ab; - } + @Data + public static class Content { + public List<String> a; + public List<String> b; + public List<String> ab; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPermittedVotingRange.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPermittedVotingRange.java index 1220941..cb88eab 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPermittedVotingRange.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritPermittedVotingRange.java
@@ -4,6 +4,6 @@ @Data public class GerritPermittedVotingRange { - private int min; - private int max; + private int min; + private int max; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReview.java index 8a6bfb9..c58ff2e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReview.java
@@ -1,22 +1,21 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit; import com.google.gson.annotations.SerializedName; +import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; -import java.util.List; -import java.util.Map; - @Data public class GerritReview { - private Map<String, List<GerritComment>> comments; - private String message; - private Labels labels; + private Map<String, List<GerritComment>> comments; + private String message; + private Labels labels; - @AllArgsConstructor - @Data - public static class Labels { - @SerializedName("Code-Review") - private int codeReview; - } + @AllArgsConstructor + @Data + public static class Labels { + @SerializedName("Code-Review") + private int codeReview; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReviewFileDiff.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReviewFileDiff.java index 7e8c81b..84d832e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReviewFileDiff.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/gerrit/GerritReviewFileDiff.java
@@ -1,18 +1,17 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.diff.DiffContent; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - @EqualsAndHashCode(callSuper = true) @Data public class GerritReviewFileDiff extends GerritFileDiff { - private List<DiffContent> content; + private List<DiffContent> content; - public GerritReviewFileDiff(Meta metaA, Meta metaB) { - this.metaA = metaA; - this.metaB = metaB; - } + public GerritReviewFileDiff(Meta metaA, Meta metaB) { + this.metaA = metaA; + this.metaB = metaB; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatDialogueItem.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatDialogueItem.java index 338a70f..a57d6b9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatDialogueItem.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatDialogueItem.java
@@ -4,8 +4,8 @@ @Data public abstract class AIChatDialogueItem { - protected Integer id; - protected String filename; - protected Integer lineNumber; - protected String codeSnippet; + protected Integer id; + protected String filename; + protected Integer lineNumber; + protected String codeSnippet; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatMessageItem.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatMessageItem.java index 031bc73..dbf94d5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatMessageItem.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatMessageItem.java
@@ -1,13 +1,12 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - @EqualsAndHashCode(callSuper = true) @Data public class AIChatMessageItem extends AIChatDialogueItem { - private String request; - private List<AIChatRequestMessage> history; + private String request; + private List<AIChatRequestMessage> history; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatReplyItem.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatReplyItem.java index eef7929..2e1283d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatReplyItem.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatReplyItem.java
@@ -6,9 +6,9 @@ @EqualsAndHashCode(callSuper = true) @Data public class AIChatReplyItem extends AIChatDialogueItem { - private String reply; - private Integer score; - private Double relevance; - private boolean repeated; - private boolean conflicting; + private String reply; + private Integer score; + private Double relevance; + private boolean repeated; + private boolean conflicting; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatRequestMessage.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatRequestMessage.java index f476d15..39969ca 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatRequestMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatRequestMessage.java
@@ -6,8 +6,8 @@ @Data @Builder public class AIChatRequestMessage { - private String role; - private String content; - // PatchSet changeId passed in the request - private String changeId; + private String role; + private String content; + // PatchSet changeId passed in the request + private String changeId; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseContent.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseContent.java index 90a425e..8d11594 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseContent.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseContent.java
@@ -1,16 +1,14 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; +import java.util.List; import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import java.util.List; - @Data @RequiredArgsConstructor public class AIChatResponseContent { - private List<AIChatReplyItem> replies; - private String changeId; - @NonNull - private String messageContent; + private List<AIChatReplyItem> replies; + private String changeId; + @NonNull private String messageContent; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseMessage.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseMessage.java index f2c85f9..d4946f8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseMessage.java
@@ -1,22 +1,23 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; import com.google.gson.annotations.SerializedName; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class AIChatResponseMessage { - private String role; - private String type; - @SerializedName("tool_calls") - private List<AIChatToolCall> toolCalls; - @SerializedName("message_creation") - private MessageCreation messageCreation; + private String role; + private String type; - @Data - public static class MessageCreation { - @SerializedName("message_id") - private String messageId; - } + @SerializedName("tool_calls") + private List<AIChatToolCall> toolCalls; + + @SerializedName("message_creation") + private MessageCreation messageCreation; + + @Data + public static class MessageCreation { + @SerializedName("message_id") + private String messageId; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseStreamed.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseStreamed.java index 81960c3..edf34b7 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseStreamed.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseStreamed.java
@@ -1,19 +1,19 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; import com.google.gson.annotations.SerializedName; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class AIChatResponseStreamed { - private List<Choice> choices; + private List<Choice> choices; - @Data - public static class Choice { - protected AIChatResponseMessage delta; - protected int index; - @SerializedName("finish_reason") - protected String finishReason; - } + @Data + public static class Choice { + protected AIChatResponseMessage delta; + protected int index; + + @SerializedName("finish_reason") + protected String finishReason; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseUnstreamed.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseUnstreamed.java index bc590e2..4463250 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseUnstreamed.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatResponseUnstreamed.java
@@ -1,15 +1,14 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class AIChatResponseUnstreamed { - private List<MessageChoice> choices; + private List<MessageChoice> choices; - @Data - public static class MessageChoice { - private AIChatResponseMessage message; - } + @Data + public static class MessageChoice { + private AIChatResponseMessage message; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatTool.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatTool.java index 176e29c..2a582cf 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatTool.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatTool.java
@@ -1,67 +1,66 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai; +import java.util.List; import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import java.util.List; - @RequiredArgsConstructor @Data public class AIChatTool { - @NonNull - private String type; - private Function function; + @NonNull private String type; + private Function function; + + @Data + public static class Function { + private String name; + private String description; + private Parameters parameters; @Data - public static class Function { - private String name; - private String description; - private Parameters parameters; + public static class Parameters { + private String type; + private Properties properties; + private List<String> required; + + @Data + public static class Properties { + private Property replies; + // Field `changeId` expected in the response to correspond with the PatchSet changeId in the + // request + private Field changeId; @Data - public static class Parameters { + public static class Property { + private String type; + private Item items; + + @Data + public static class Item { private String type; - private Properties properties; + private ObjectProperties properties; private List<String> required; @Data - public static class Properties { - private Property replies; - // Field `changeId` expected in the response to correspond with the PatchSet changeId in the request - private Field changeId; - - @Data - public static class Property { - private String type; - private Item items; - - @Data - public static class Item { - private String type; - private ObjectProperties properties; - private List<String> required; - - @Data - public static class ObjectProperties { - private Field id; - private Field reply; - private Field score; - private Field relevance; - private Field repeated; - private Field conflicting; - private Field filename; - private Field lineNumber; - private Field codeSnippet; - } - } - } - - @Data - public static class Field { - private String type; - } + public static class ObjectProperties { + private Field id; + private Field reply; + private Field score; + private Field relevance; + private Field repeated; + private Field conflicting; + private Field filename; + private Field lineNumber; + private Field codeSnippet; } + } } + + @Data + public static class Field { + private String type; + } + } } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolCall.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolCall.java index 605015f..1a71fe8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolCall.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolCall.java
@@ -4,13 +4,13 @@ @Data public class AIChatToolCall { - private String id; - private String type; - private Function function; + private String id; + private String type; + private Function function; - @Data - public static class Function { - private String name; - private String arguments; - } + @Data + public static class Function { + private String name; + private String arguments; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolChoice.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolChoice.java index 1ba0478..fd05acf 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolChoice.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/api/openai/AIChatToolChoice.java
@@ -4,11 +4,11 @@ @Data public class AIChatToolChoice { - private String type; - private Function function; + private String type; + private Function function; - @Data - public static class Function { - private String name; - } + @Data + public static class Function { + private String name; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/ChangeSetData.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/ChangeSetData.java index 5b5fa4f..caad845 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/ChangeSetData.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/ChangeSetData.java
@@ -1,40 +1,36 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data; +import java.util.HashSet; +import java.util.Set; import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import java.util.HashSet; -import java.util.Set; - @RequiredArgsConstructor @Data @Slf4j public class ChangeSetData { - @NonNull - private Integer gptAccountId; - private String reviewAIDataPrompt; - private Integer commentPropertiesSize; - @NonNull - private Integer votingMinScore; - @NonNull - private Integer votingMaxScore; + @NonNull private Integer gptAccountId; + private String reviewAIDataPrompt; + private Integer commentPropertiesSize; + @NonNull private Integer votingMinScore; + @NonNull private Integer votingMaxScore; - // Command variables - private Boolean forcedReview = false; - private Boolean forcedReviewLastPatchSet = false; - private Boolean replyFilterEnabled = true; - private Boolean debugReviewMode = false; - private Boolean hideAICodeReview = false; - private Set<String> directives = new HashSet<>(); - private String reviewSystemMessage; + // Command variables + private Boolean forcedReview = false; + private Boolean forcedReviewLastPatchSet = false; + private Boolean replyFilterEnabled = true; + private Boolean debugReviewMode = false; + private Boolean hideAICodeReview = false; + private Set<String> directives = new HashSet<>(); + private String reviewSystemMessage; - public Boolean shouldHideAICodeReview() { - return hideAICodeReview && !forcedReview; - } + public Boolean shouldHideAICodeReview() { + return hideAICodeReview && !forcedReview; + } - public Boolean shouldRequestAICodeReview() { - return reviewSystemMessage == null && !shouldHideAICodeReview(); - } + public Boolean shouldRequestAICodeReview() { + return reviewSystemMessage == null && !shouldHideAICodeReview(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/CommentData.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/CommentData.java index b7085ab..afab316 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/CommentData.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/CommentData.java
@@ -1,16 +1,15 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; -import lombok.AllArgsConstructor; -import lombok.Data; - import java.util.HashMap; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; @AllArgsConstructor @Data public class CommentData { - private List<GerritComment> commentProperties; - private HashMap<String, GerritComment> commentMap; - private HashMap<String, GerritComment> patchSetCommentMap; + private List<GerritComment> commentProperties; + private HashMap<String, GerritComment> commentMap; + private HashMap<String, GerritComment> patchSetCommentMap; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/GerritClientData.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/GerritClientData.java index 8dfa887..3f16a94 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/GerritClientData.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/data/GerritClientData.java
@@ -2,25 +2,24 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.diff.FileDiffProcessed; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritComment; -import lombok.AllArgsConstructor; -import lombok.Data; - import java.util.HashMap; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; @AllArgsConstructor @Data public class GerritClientData { - private HashMap<String, FileDiffProcessed> fileDiffsProcessed; - private List<GerritComment> detailComments; - private CommentData commentData; - private Integer revisionBase; + private HashMap<String, FileDiffProcessed> fileDiffsProcessed; + private List<GerritComment> detailComments; + private CommentData commentData; + private Integer revisionBase; - public List<GerritComment> getCommentProperties() { - return commentData.getCommentProperties(); - } + public List<GerritComment> getCommentProperties() { + return commentData.getCommentProperties(); + } - public int getOneBasedRevisionBase() { - return revisionBase +1; - } + public int getOneBasedRevisionBase() { + return revisionBase + 1; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/code/CodeFinderDiff.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/code/CodeFinderDiff.java index cd826d1..8637425 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/code/CodeFinderDiff.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/code/CodeFinderDiff.java
@@ -1,14 +1,13 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.code; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.diff.DiffContent; +import java.util.TreeMap; import lombok.AllArgsConstructor; import lombok.Data; -import java.util.TreeMap; - @AllArgsConstructor @Data public class CodeFinderDiff { - private DiffContent content; - private TreeMap<Integer, Integer> charToLineMap; + private DiffContent content; + private TreeMap<Integer, Integer> charToLineMap; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/diff/DiffContent.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/diff/DiffContent.java index 620acc9..f3bfb91 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/diff/DiffContent.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/patch/diff/DiffContent.java
@@ -4,7 +4,7 @@ @Data public class DiffContent { - public String a; - public String b; - public String ab; + public String a; + public String b; + public String ab; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/review/ReviewBatch.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/review/ReviewBatch.java index cb1f356..0281aec 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/review/ReviewBatch.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/common/model/review/ReviewBatch.java
@@ -1,23 +1,22 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.review; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; + import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit.GerritCodeRange; import lombok.Data; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; - @Data @RequiredArgsConstructor public class ReviewBatch { - private String id; - @NonNull - private String content; - private String filename; - private Integer line; - private GerritCodeRange range; + private String id; + @NonNull private String content; + private String filename; + private Integer line; + private GerritCodeRange range; - public String getFilename() { - return filename == null ? GERRIT_PATCH_SET_FILENAME : filename; - } + public String getFilename() { + return filename == null ? GERRIT_PATCH_SET_FILENAME : filename; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/UriResourceLocatorStateful.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/UriResourceLocatorStateful.java index 4525c25..233770f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/UriResourceLocatorStateful.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/UriResourceLocatorStateful.java
@@ -1,49 +1,49 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api; public class UriResourceLocatorStateful { - private static final String VERSION_URI = "/v1"; + private static final String VERSION_URI = "/v1"; - public static String filesCreateUri() { - return VERSION_URI + "/files"; - } + public static String filesCreateUri() { + return VERSION_URI + "/files"; + } - public static String assistantCreateUri() { - return VERSION_URI + "/assistants"; - } + public static String assistantCreateUri() { + return VERSION_URI + "/assistants"; + } - public static String threadsUri() { - return VERSION_URI + "/threads"; - } + public static String threadsUri() { + return VERSION_URI + "/threads"; + } - public static String threadRetrieveUri(String threadId) { - return threadsUri() + "/" + threadId; - } + public static String threadRetrieveUri(String threadId) { + return threadsUri() + "/" + threadId; + } - public static String threadMessagesUri(String threadId) { - return threadRetrieveUri(threadId) + "/messages"; - } + public static String threadMessagesUri(String threadId) { + return threadRetrieveUri(threadId) + "/messages"; + } - public static String threadMessageRetrieveUri(String threadId, String messageId) { - return threadMessagesUri(threadId) + "/" + messageId; - } + public static String threadMessageRetrieveUri(String threadId, String messageId) { + return threadMessagesUri(threadId) + "/" + messageId; + } - public static String runsUri(String threadId) { - return threadRetrieveUri(threadId) + "/runs"; - } + public static String runsUri(String threadId) { + return threadRetrieveUri(threadId) + "/runs"; + } - public static String runRetrieveUri(String threadId, String runId) { - return runsUri(threadId) + "/" + runId; - } + public static String runRetrieveUri(String threadId, String runId) { + return runsUri(threadId) + "/" + runId; + } - public static String runStepsUri(String threadId, String runId) { - return runRetrieveUri(threadId, runId) + "/steps"; - } + public static String runStepsUri(String threadId, String runId) { + return runRetrieveUri(threadId, runId) + "/steps"; + } - public static String runCancelUri(String threadId, String runId) { - return runRetrieveUri(threadId, runId) + "/cancel"; - } + public static String runCancelUri(String threadId, String runId) { + return runRetrieveUri(threadId, runId) + "/cancel"; + } - public static String vectorStoreCreateUri() { - return VERSION_URI + "/vector_stores"; - } + public static String vectorStoreCreateUri() { + return VERSION_URI + "/vector_stores"; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatai/AIChatClientStateful.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatai/AIChatClientStateful.java index 9c586fe..82b083c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatai/AIChatClientStateful.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatai/AIChatClientStateful.java
@@ -1,13 +1,17 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatai; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import static com.googlesource.gerrit.plugins.aicodereview.utils.JsonTextUtils.isJsonString; +import static com.googlesource.gerrit.plugins.aicodereview.utils.JsonTextUtils.unwrapJsonCode; + import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi.ChatAIClient; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptRun; @@ -17,93 +21,81 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptThreadMessageResponse; import lombok.extern.slf4j.Slf4j; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; -import static com.googlesource.gerrit.plugins.aicodereview.utils.JsonTextUtils.isJsonString; -import static com.googlesource.gerrit.plugins.aicodereview.utils.JsonTextUtils.unwrapJsonCode; - @Slf4j @Singleton public class AIChatClientStateful extends AIChatClient implements ChatAIClient { - private static final String TYPE_MESSAGE_CREATION = "message_creation"; - private static final String TYPE_TOOL_CALLS = "tool_calls"; + private static final String TYPE_MESSAGE_CREATION = "message_creation"; + private static final String TYPE_TOOL_CALLS = "tool_calls"; - private final GitRepoFiles gitRepoFiles; - private final PluginDataHandlerProvider pluginDataHandlerProvider; + private final GitRepoFiles gitRepoFiles; + private final PluginDataHandlerProvider pluginDataHandlerProvider; - @VisibleForTesting - @Inject - public AIChatClientStateful( - Configuration config, - GitRepoFiles gitRepoFiles, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - super(config); - this.gitRepoFiles = gitRepoFiles; - this.pluginDataHandlerProvider = pluginDataHandlerProvider; + @VisibleForTesting + @Inject + public AIChatClientStateful( + Configuration config, + GitRepoFiles gitRepoFiles, + PluginDataHandlerProvider pluginDataHandlerProvider) { + super(config); + this.gitRepoFiles = gitRepoFiles; + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + } + + public AIChatResponseContent ask( + ChangeSetData changeSetData, GerritChange change, String patchSet) { + isCommentEvent = change.getIsCommentEvent(); + String changeId = change.getFullChangeId(); + log.info( + "Processing STATEFUL ChatGPT Request with changeId: {}, Patch Set: {}", changeId, patchSet); + + ChatGptThread chatGptThread = new ChatGptThread(config, pluginDataHandlerProvider); + String threadId = chatGptThread.createThread(); + + ChatGptThreadMessage chatGptThreadMessage = + new ChatGptThreadMessage(threadId, config, changeSetData, change, patchSet); + chatGptThreadMessage.addMessage(); + + ChatGptRun chatGptRun = + new ChatGptRun( + threadId, config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider); + chatGptRun.createRun(); + chatGptRun.pollRunStep(); + // Attribute `requestBody` is valued for testing purposes + requestBody = chatGptThreadMessage.getAddMessageRequestBody(); + log.debug("ChatGPT request body: {}", requestBody); + + AIChatResponseContent AIChatResponseContent = getResponseContentStateful(threadId, chatGptRun); + chatGptRun.cancelRun(); + + return AIChatResponseContent; + } + + private AIChatResponseContent getResponseContentStateful(String threadId, ChatGptRun chatGptRun) { + return switch (chatGptRun.getFirstStepDetails().getType()) { + case TYPE_MESSAGE_CREATION -> retrieveThreadMessage(threadId, chatGptRun); + case TYPE_TOOL_CALLS -> getResponseContent(chatGptRun.getFirstStepToolCalls()); + default -> + throw new IllegalStateException( + "Unexpected Step Type in stateful ChatGpt response: " + chatGptRun); + }; + } + + private AIChatResponseContent retrieveThreadMessage(String threadId, ChatGptRun chatGptRun) { + ChatGptThreadMessage chatGptThreadMessage = new ChatGptThreadMessage(threadId, config); + ChatGptThreadMessageResponse threadMessageResponse = + chatGptThreadMessage.retrieveMessage( + chatGptRun.getFirstStepDetails().getMessageCreation().getMessageId()); + String responseText = threadMessageResponse.getContent().get(0).getText().getValue(); + if (responseText == null) { + throw new RuntimeException("ChatGPT thread message response content is null"); } - - public AIChatResponseContent ask(ChangeSetData changeSetData, GerritChange change, String patchSet) { - isCommentEvent = change.getIsCommentEvent(); - String changeId = change.getFullChangeId(); - log.info("Processing STATEFUL ChatGPT Request with changeId: {}, Patch Set: {}", changeId, patchSet); - - ChatGptThread chatGptThread = new ChatGptThread(config, pluginDataHandlerProvider); - String threadId = chatGptThread.createThread(); - - ChatGptThreadMessage chatGptThreadMessage = new ChatGptThreadMessage( - threadId, - config, - changeSetData, - change, - patchSet - ); - chatGptThreadMessage.addMessage(); - - ChatGptRun chatGptRun = new ChatGptRun( - threadId, - config, - changeSetData, - change, - gitRepoFiles, - pluginDataHandlerProvider - ); - chatGptRun.createRun(); - chatGptRun.pollRunStep(); - // Attribute `requestBody` is valued for testing purposes - requestBody = chatGptThreadMessage.getAddMessageRequestBody(); - log.debug("ChatGPT request body: {}", requestBody); - - AIChatResponseContent AIChatResponseContent = getResponseContentStateful(threadId, chatGptRun); - chatGptRun.cancelRun(); - - return AIChatResponseContent; + if (isJsonString(responseText)) { + return extractResponseContent(responseText); } + return new AIChatResponseContent(responseText); + } - private AIChatResponseContent getResponseContentStateful(String threadId, ChatGptRun chatGptRun) { - return switch (chatGptRun.getFirstStepDetails().getType()) { - case TYPE_MESSAGE_CREATION -> retrieveThreadMessage(threadId, chatGptRun); - case TYPE_TOOL_CALLS -> getResponseContent(chatGptRun.getFirstStepToolCalls()); - default -> throw new IllegalStateException("Unexpected Step Type in stateful ChatGpt response: " + - chatGptRun); - }; - } - - private AIChatResponseContent retrieveThreadMessage(String threadId, ChatGptRun chatGptRun) { - ChatGptThreadMessage chatGptThreadMessage = new ChatGptThreadMessage(threadId, config); - ChatGptThreadMessageResponse threadMessageResponse = chatGptThreadMessage.retrieveMessage( - chatGptRun.getFirstStepDetails().getMessageCreation().getMessageId() - ); - String responseText = threadMessageResponse.getContent().get(0).getText().getValue(); - if (responseText == null) { - throw new RuntimeException("ChatGPT thread message response content is null"); - } - if (isJsonString(responseText)) { - return extractResponseContent(responseText); - } - return new AIChatResponseContent(responseText); - } - - private AIChatResponseContent extractResponseContent(String responseText) { - return getGson().fromJson(unwrapJsonCode(responseText), AIChatResponseContent.class); - } + private AIChatResponseContent extractResponseContent(String responseText) { + return getGson().fromJson(unwrapJsonCode(responseText), AIChatResponseContent.class); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptAssistant.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptAssistant.java index 7f99dff..b7ad49b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptAssistant.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptAssistant.java
@@ -1,163 +1,158 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; -import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; -import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; -import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; -import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatParameters; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatTools; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatTool; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git.GitRepoFiles; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt.AIChatGptPromptStatefulBase; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.*; -import com.googlesource.gerrit.plugins.aicodereview.utils.HashUtils; -import lombok.extern.slf4j.Slf4j; -import okhttp3.Request; - -import java.net.URI; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatPromptFactory.getAIChatPromptStateful; import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptVectorStore.KEY_VECTOR_STORE_ID; import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.createTempFileWithContent; import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.sanitizeFilename; import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; +import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; +import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; +import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatParameters; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatTools; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatTool; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git.GitRepoFiles; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt.AIChatGptPromptStatefulBase; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptCreateAssistantRequestBody; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptFilesResponse; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptResponse; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptToolResources; +import com.googlesource.gerrit.plugins.aicodereview.utils.HashUtils; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; + @Slf4j public class ChatGptAssistant extends ClientBase { - private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); - private final ChangeSetData changeSetData; - private final GerritChange change; - private final GitRepoFiles gitRepoFiles; - private final PluginDataHandler projectDataHandler; - private final PluginDataHandler assistantsDataHandler; + private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); + private final ChangeSetData changeSetData; + private final GerritChange change; + private final GitRepoFiles gitRepoFiles; + private final PluginDataHandler projectDataHandler; + private final PluginDataHandler assistantsDataHandler; - private String description; - private String instructions; - private String model; - private Double temperature; + private String description; + private String instructions; + private String model; + private Double temperature; - public ChatGptAssistant( - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - GitRepoFiles gitRepoFiles, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - super(config); - this.changeSetData = changeSetData; - this.change = change; - this.gitRepoFiles = gitRepoFiles; - this.projectDataHandler = pluginDataHandlerProvider.getProjectScope(); - this.assistantsDataHandler = pluginDataHandlerProvider.getAssistantsWorkspace(); + public ChatGptAssistant( + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + GitRepoFiles gitRepoFiles, + PluginDataHandlerProvider pluginDataHandlerProvider) { + super(config); + this.changeSetData = changeSetData; + this.change = change; + this.gitRepoFiles = gitRepoFiles; + this.projectDataHandler = pluginDataHandlerProvider.getProjectScope(); + this.assistantsDataHandler = pluginDataHandlerProvider.getAssistantsWorkspace(); + } + + public String setupAssistant() { + setupAssistantParameters(); + String assistantIdHashKey = calculateAssistantIdHashKey(); + log.info("Calculated assistant id hash key: {}", assistantIdHashKey); + String assistantId = assistantsDataHandler.getValue(assistantIdHashKey); + if (assistantId == null || config.getForceCreateAssistant()) { + log.debug("Setup Assistant for project {}", change.getProjectNameKey()); + String vectorStoreId = createVectorStore(); + assistantId = createAssistant(vectorStoreId); + assistantsDataHandler.setValue(assistantIdHashKey, assistantId); + log.info("Project assistant created with ID: {}", assistantId); + } else { + log.info("Project assistant found for the project. Assistant ID: {}", assistantId); } + return assistantId; + } - public String setupAssistant() { - setupAssistantParameters(); - String assistantIdHashKey = calculateAssistantIdHashKey(); - log.info("Calculated assistant id hash key: {}", assistantIdHashKey); - String assistantId = assistantsDataHandler.getValue(assistantIdHashKey); - if (assistantId == null || config.getForceCreateAssistant()) { - log.debug("Setup Assistant for project {}", change.getProjectNameKey()); - String vectorStoreId = createVectorStore(); - assistantId = createAssistant(vectorStoreId); - assistantsDataHandler.setValue(assistantIdHashKey, assistantId); - log.info("Project assistant created with ID: {}", assistantId); - } - else { - log.info("Project assistant found for the project. Assistant ID: {}", assistantId); - } - return assistantId; + public String createVectorStore() { + String vectorStoreId = projectDataHandler.getValue(KEY_VECTOR_STORE_ID); + if (vectorStoreId == null) { + String fileId = uploadRepoFiles(); + ChatGptVectorStore vectorStore = new ChatGptVectorStore(fileId, config, change); + ChatGptResponse createVectorStoreResponse = vectorStore.createVectorStore(); + vectorStoreId = createVectorStoreResponse.getId(); + projectDataHandler.setValue(KEY_VECTOR_STORE_ID, vectorStoreId); + log.info("Vector Store created with ID: {}", vectorStoreId); + } else { + log.info("Vector Store found for the project. Vector Store ID: {}", vectorStoreId); } + return vectorStoreId; + } - public String createVectorStore() { - String vectorStoreId = projectDataHandler.getValue(KEY_VECTOR_STORE_ID); - if (vectorStoreId == null) { - String fileId = uploadRepoFiles(); - ChatGptVectorStore vectorStore = new ChatGptVectorStore(fileId, config, change); - ChatGptResponse createVectorStoreResponse = vectorStore.createVectorStore(); - vectorStoreId = createVectorStoreResponse.getId(); - projectDataHandler.setValue(KEY_VECTOR_STORE_ID, vectorStoreId); - log.info("Vector Store created with ID: {}", vectorStoreId); - } - else { - log.info("Vector Store found for the project. Vector Store ID: {}", vectorStoreId); - } - return vectorStoreId; - } + public void flushAssistantIds() { + projectDataHandler.removeValue(KEY_VECTOR_STORE_ID); + assistantsDataHandler.destroy(); + } - public void flushAssistantIds() { - projectDataHandler.removeValue(KEY_VECTOR_STORE_ID); - assistantsDataHandler.destroy(); - } + private String uploadRepoFiles() { + String repoFiles = gitRepoFiles.getGitRepoFiles(config, change); + Path repoPath = + createTempFileWithContent(sanitizeFilename(change.getProjectName()), ".json", repoFiles); + ChatGptFiles chatGptFiles = new ChatGptFiles(config); + ChatGptFilesResponse chatGptFilesResponse = chatGptFiles.uploadFiles(repoPath); - private String uploadRepoFiles() { - String repoFiles = gitRepoFiles.getGitRepoFiles(config, change); - Path repoPath = createTempFileWithContent(sanitizeFilename(change.getProjectName()), ".json", repoFiles); - ChatGptFiles chatGptFiles = new ChatGptFiles(config); - ChatGptFilesResponse chatGptFilesResponse = chatGptFiles.uploadFiles(repoPath); + return chatGptFilesResponse.getId(); + } - return chatGptFilesResponse.getId(); - } + private String createAssistant(String vectorStoreId) { + Request request = createRequest(vectorStoreId); + log.debug("ChatGPT Create Assistant request: {}", request); - private String createAssistant(String vectorStoreId) { - Request request = createRequest(vectorStoreId); - log.debug("ChatGPT Create Assistant request: {}", request); + ChatGptResponse assistantResponse = + getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); + log.debug("Assistant created: {}", assistantResponse); - ChatGptResponse assistantResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); - log.debug("Assistant created: {}", assistantResponse); + return assistantResponse.getId(); + } - return assistantResponse.getId(); - } + private Request createRequest(String vectorStoreId) { + URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.assistantCreateUri()); + log.debug("ChatGPT Create Assistant request URI: {}", uri); + AIChatTool[] tools = + new AIChatTool[] {new AIChatTool("file_search"), AIChatTools.retrieveFormatRepliesTool()}; + ChatGptToolResources toolResources = + new ChatGptToolResources( + new ChatGptToolResources.VectorStoreIds(new String[] {vectorStoreId})); + ChatGptCreateAssistantRequestBody requestBody = + ChatGptCreateAssistantRequestBody.builder() + .name(AIChatGptPromptStatefulBase.DEFAULT_AI_CHAT_ASSISTANT_NAME) + .description(description) + .instructions(instructions) + .model(model) + .temperature(temperature) + .tools(tools) + .toolResources(toolResources) + .build(); + log.debug("ChatGPT Create Assistant request body: {}", requestBody); - private Request createRequest(String vectorStoreId) { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.assistantCreateUri()); - log.debug("ChatGPT Create Assistant request URI: {}", uri); - AIChatTool[] tools = new AIChatTool[] { - new AIChatTool("file_search"), - AIChatTools.retrieveFormatRepliesTool() - }; - ChatGptToolResources toolResources = new ChatGptToolResources( - new ChatGptToolResources.VectorStoreIds( - new String[] {vectorStoreId} - ) - ); - ChatGptCreateAssistantRequestBody requestBody = ChatGptCreateAssistantRequestBody.builder() - .name(AIChatGptPromptStatefulBase.DEFAULT_AI_CHAT_ASSISTANT_NAME) - .description(description) - .instructions(instructions) - .model(model) - .temperature(temperature) - .tools(tools) - .toolResources(toolResources) - .build(); - log.debug("ChatGPT Create Assistant request body: {}", requestBody); + return httpClient.createRequestFromJson(uri.toString(), config, requestBody); + } - return httpClient.createRequestFromJson(uri.toString(), config, requestBody); - } + private void setupAssistantParameters() { + ChatGptPromptStateful chatGptPromptStateful = + getAIChatPromptStateful(config, changeSetData, change); + AIChatParameters AIChatParameters = new AIChatParameters(config, change.getIsCommentEvent()); - private void setupAssistantParameters() { - ChatGptPromptStateful chatGptPromptStateful = getAIChatPromptStateful(config, changeSetData, change); - AIChatParameters AIChatParameters = new AIChatParameters(config, change.getIsCommentEvent()); + description = chatGptPromptStateful.getDefaultGptAssistantDescription(); + instructions = chatGptPromptStateful.getDefaultGptAssistantInstructions(); + model = config.getAIModel(); + temperature = AIChatParameters.getGptTemperature(); + } - description = chatGptPromptStateful.getDefaultGptAssistantDescription(); - instructions = chatGptPromptStateful.getDefaultGptAssistantInstructions(); - model = config.getAIModel(); - temperature = AIChatParameters.getGptTemperature(); - } - - private String calculateAssistantIdHashKey() { - return HashUtils.hashData(new ArrayList<>(List.of( - description, - instructions, - model, - temperature.toString() - ))); - } + private String calculateAssistantIdHashKey() { + return HashUtils.hashData( + new ArrayList<>(List.of(description, instructions, model, temperature.toString()))); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptFiles.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptFiles.java index 22d1787..b705c47 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptFiles.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptFiles.java
@@ -1,49 +1,50 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.http.HttpClient; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptFilesResponse; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; -import org.apache.http.NameValuePair; - import java.io.File; import java.net.URI; import java.nio.file.Path; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; @Slf4j public class ChatGptFiles extends ClientBase { - private final HttpClient httpClient = new HttpClient(); + private final HttpClient httpClient = new HttpClient(); - public ChatGptFiles(Configuration config) { - super(config); - } + public ChatGptFiles(Configuration config) { + super(config); + } - public ChatGptFilesResponse uploadFiles(Path repoPath) { - Request request = createUploadFileRequest(repoPath); - log.debug("ChatGPT Upload Files request: {}", request); + public ChatGptFilesResponse uploadFiles(Path repoPath) { + Request request = createUploadFileRequest(repoPath); + log.debug("ChatGPT Upload Files request: {}", request); - String response = httpClient.execute(request); - log.debug("ChatGPT Upload Files response: {}", response); + String response = httpClient.execute(request); + log.debug("ChatGPT Upload Files response: {}", response); - return getGson().fromJson(response, ChatGptFilesResponse.class); - } + return getGson().fromJson(response, ChatGptFilesResponse.class); + } - private Request createUploadFileRequest(Path repoPath) { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.filesCreateUri()); - log.debug("ChatGPT Upload Files request URI: {}", uri); - File file = repoPath.toFile(); - RequestBody requestBody = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("purpose", "assistants") - .addFormDataPart("file", file.getName(), - RequestBody.create(file, MediaType.parse("application/json"))) - .build(); + private Request createUploadFileRequest(Path repoPath) { + URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.filesCreateUri()); + log.debug("ChatGPT Upload Files request URI: {}", uri); + File file = repoPath.toFile(); + RequestBody requestBody = + new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("purpose", "assistants") + .addFormDataPart( + "file", + file.getName(), + RequestBody.create(file, MediaType.parse("application/json"))) + .build(); - return httpClient.createRequest(uri.toString(), config, requestBody, null); - } + return httpClient.createRequest(uri.toString(), config, requestBody, null); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptHttpClient.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptHttpClient.java index 2ccc552..a363632 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptHttpClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptHttpClient.java
@@ -2,14 +2,15 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.http.HttpClient; +import java.util.Map; import okhttp3.Request; -import java.util.Map; - public class ChatGptHttpClient extends HttpClient { - private static final Map<String, String> BETA_VERSION_HEADER = Map.of("OpenAI-Beta", "assistants=v2"); + private static final Map<String, String> BETA_VERSION_HEADER = + Map.of("OpenAI-Beta", "assistants=v2"); - public Request createRequestFromJson(String uri, Configuration configuration, Object requestObject) { - return createRequestFromJson(uri, configuration, requestObject, BETA_VERSION_HEADER); - } + public Request createRequestFromJson( + String uri, Configuration configuration, Object requestObject) { + return createRequestFromJson(uri, configuration, requestObject, BETA_VERSION_HEADER); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptRun.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptRun.java index 3325105..9b3668a 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptRun.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptRun.java
@@ -1,5 +1,8 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import static com.googlesource.gerrit.plugins.aicodereview.utils.ThreadUtils.threadSleep; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; @@ -9,172 +12,170 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git.GitRepoFiles; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.*; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptCreateRunRequest; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptListResponse; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptResponse; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptRunStepsResponse; +import java.net.URI; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import lombok.extern.slf4j.Slf4j; import okhttp3.Request; -import java.net.URI; -import java.util.*; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; -import static com.googlesource.gerrit.plugins.aicodereview.utils.ThreadUtils.threadSleep; - @Slf4j public class ChatGptRun extends ClientBase { - private static final int RUN_POLLING_INTERVAL = 1000; - private static final int STEP_RETRIEVAL_INTERVAL = 10000; - private static final int MAX_STEP_RETRIEVAL_RETRIES = 3; - private static final Set<String> UNCOMPLETED_STATUSES = new HashSet<>(Arrays.asList( - "queued", - "in_progress", - "cancelling" - )); - public static final String COMPLETED_STATUS = "completed"; - public static final String CANCELLED_STATUS = "cancelled"; + private static final int RUN_POLLING_INTERVAL = 1000; + private static final int STEP_RETRIEVAL_INTERVAL = 10000; + private static final int MAX_STEP_RETRIEVAL_RETRIES = 3; + private static final Set<String> UNCOMPLETED_STATUSES = + new HashSet<>(Arrays.asList("queued", "in_progress", "cancelling")); + public static final String COMPLETED_STATUS = "completed"; + public static final String CANCELLED_STATUS = "cancelled"; - private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); - private final ChangeSetData changeSetData; - private final GerritChange change; - private final String threadId; - private final GitRepoFiles gitRepoFiles; - private final PluginDataHandlerProvider pluginDataHandlerProvider; + private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); + private final ChangeSetData changeSetData; + private final GerritChange change; + private final String threadId; + private final GitRepoFiles gitRepoFiles; + private final PluginDataHandlerProvider pluginDataHandlerProvider; - private ChatGptResponse runResponse; - private ChatGptListResponse stepResponse; - private String assistantId; + private ChatGptResponse runResponse; + private ChatGptListResponse stepResponse; + private String assistantId; - public ChatGptRun( - String threadId, - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - GitRepoFiles gitRepoFiles, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - super(config); - this.changeSetData = changeSetData; - this.change = change; - this.threadId = threadId; - this.gitRepoFiles = gitRepoFiles; - this.pluginDataHandlerProvider = pluginDataHandlerProvider; + public ChatGptRun( + String threadId, + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + GitRepoFiles gitRepoFiles, + PluginDataHandlerProvider pluginDataHandlerProvider) { + super(config); + this.changeSetData = changeSetData; + this.change = change; + this.threadId = threadId; + this.gitRepoFiles = gitRepoFiles; + this.pluginDataHandlerProvider = pluginDataHandlerProvider; + } + + public void createRun() { + ChatGptAssistant chatGptAssistant = + new ChatGptAssistant( + config, changeSetData, change, gitRepoFiles, pluginDataHandlerProvider); + assistantId = chatGptAssistant.setupAssistant(); + + Request request = runCreateRequest(); + log.info("ChatGPT Create Run request: {}", request); + + runResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); + log.info("Run created: {}", runResponse); + } + + public void pollRunStep() { + for (int retries = 0; retries < MAX_STEP_RETRIEVAL_RETRIES; retries++) { + int pollingCount = pollRun(); + + Request stepsRequest = getStepsRequest(); + log.debug("ChatGPT Retrieve Run Steps request: {}", stepsRequest); + + String response = httpClient.execute(stepsRequest); + stepResponse = getGson().fromJson(response, ChatGptListResponse.class); + log.info("Run executed after {} polling requests: {}", pollingCount, stepResponse); + if (stepResponse.getData().isEmpty()) { + log.warn("Empty response from ChatGPT"); + threadSleep(STEP_RETRIEVAL_INTERVAL); + continue; + } + return; } + } - public void createRun() { - ChatGptAssistant chatGptAssistant = new ChatGptAssistant( - config, - changeSetData, - change, - gitRepoFiles, - pluginDataHandlerProvider - ); - assistantId = chatGptAssistant.setupAssistant(); + public AIChatResponseMessage getFirstStepDetails() { + return getFirstStep().getStepDetails(); + } - Request request = runCreateRequest(); - log.info("ChatGPT Create Run request: {}", request); + public List<AIChatToolCall> getFirstStepToolCalls() { + return getFirstStepDetails().getToolCalls(); + } - runResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); - log.info("Run created: {}", runResponse); + public void cancelRun() { + if (getFirstStep().getStatus().equals(COMPLETED_STATUS)) return; + + Request cancelRequest = getCancelRequest(); + log.debug("ChatGPT Cancel Run request: {}", cancelRequest); + try { + String fullResponse = httpClient.execute(cancelRequest); + log.debug("ChatGPT Cancel Run Full response: {}", fullResponse); + ChatGptResponse response = getGson().fromJson(fullResponse, ChatGptResponse.class); + if (!response.getStatus().equals(CANCELLED_STATUS)) { + log.error("Unable to cancel run. Run cancel response: {}", fullResponse); + } + } catch (Exception e) { + log.error("Error cancelling run", e); } + } - public void pollRunStep() { - for (int retries = 0; retries < MAX_STEP_RETRIEVAL_RETRIES; retries++) { - int pollingCount = pollRun(); + private int pollRun() { + int pollingCount = 0; - Request stepsRequest = getStepsRequest(); - log.debug("ChatGPT Retrieve Run Steps request: {}", stepsRequest); - - String response = httpClient.execute(stepsRequest); - stepResponse = getGson().fromJson(response, ChatGptListResponse.class); - log.info("Run executed after {} polling requests: {}", pollingCount, stepResponse); - if (stepResponse.getData().isEmpty()) { - log.warn("Empty response from ChatGPT"); - threadSleep(STEP_RETRIEVAL_INTERVAL); - continue; - } - return; - } + while (UNCOMPLETED_STATUSES.contains(runResponse.getStatus())) { + pollingCount++; + log.debug("Polling request #{}", pollingCount); + threadSleep(RUN_POLLING_INTERVAL); + Request pollRequest = getPollRequest(); + log.debug("ChatGPT Poll Run request: {}", pollRequest); + runResponse = getGson().fromJson(httpClient.execute(pollRequest), ChatGptResponse.class); + log.debug("ChatGPT Run response: {}", runResponse); } + return pollingCount; + } - public AIChatResponseMessage getFirstStepDetails() { - return getFirstStep().getStepDetails(); - } + private ChatGptRunStepsResponse getFirstStep() { + return stepResponse.getData().get(0); + } - public List<AIChatToolCall> getFirstStepToolCalls() { - return getFirstStepDetails().getToolCalls(); - } + private Request runCreateRequest() { + URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.runsUri(threadId)); + log.debug("ChatGPT Create Run request URI: {}", uri); + ChatGptCreateRunRequest requestBody = + ChatGptCreateRunRequest.builder().assistantId(assistantId).build(); - public void cancelRun() { - if (getFirstStep().getStatus().equals(COMPLETED_STATUS)) return; + return httpClient.createRequestFromJson(uri.toString(), config, requestBody); + } - Request cancelRequest = getCancelRequest(); - log.debug("ChatGPT Cancel Run request: {}", cancelRequest); - try { - String fullResponse = httpClient.execute(cancelRequest); - log.debug("ChatGPT Cancel Run Full response: {}", fullResponse); - ChatGptResponse response = getGson().fromJson(fullResponse, ChatGptResponse.class); - if (!response.getStatus().equals(CANCELLED_STATUS)) { - log.error("Unable to cancel run. Run cancel response: {}", fullResponse); - } - } - catch (Exception e) { - log.error("Error cancelling run", e); - } - } - - private int pollRun() { - int pollingCount = 0; - - while (UNCOMPLETED_STATUSES.contains(runResponse.getStatus())) { - pollingCount++; - log.debug("Polling request #{}", pollingCount); - threadSleep(RUN_POLLING_INTERVAL); - Request pollRequest = getPollRequest(); - log.debug("ChatGPT Poll Run request: {}", pollRequest); - runResponse = getGson().fromJson(httpClient.execute(pollRequest), ChatGptResponse.class); - log.debug("ChatGPT Run response: {}", runResponse); - } - return pollingCount; - } - - private ChatGptRunStepsResponse getFirstStep() { - return stepResponse.getData().get(0); - } - - private Request runCreateRequest() { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.runsUri(threadId)); - log.debug("ChatGPT Create Run request URI: {}", uri); - ChatGptCreateRunRequest requestBody = ChatGptCreateRunRequest.builder() - .assistantId(assistantId) - .build(); - - return httpClient.createRequestFromJson(uri.toString(), config, requestBody); - } - - private Request getPollRequest() { - URI uri = URI.create(config.getAIDomain() + private Request getPollRequest() { + URI uri = + URI.create( + config.getAIDomain() + UriResourceLocatorStateful.runRetrieveUri(threadId, runResponse.getId())); - log.debug("ChatGPT Poll Run request URI: {}", uri); + log.debug("ChatGPT Poll Run request URI: {}", uri); - return getRunPollRequest(uri); - } + return getRunPollRequest(uri); + } - private Request getStepsRequest() { - URI uri = URI.create(config.getAIDomain() + private Request getStepsRequest() { + URI uri = + URI.create( + config.getAIDomain() + UriResourceLocatorStateful.runStepsUri(threadId, runResponse.getId())); - log.debug("ChatGPT Run Steps request URI: {}", uri); + log.debug("ChatGPT Run Steps request URI: {}", uri); - return getRunPollRequest(uri); - } + return getRunPollRequest(uri); + } - private Request getCancelRequest() { - URI uri = URI.create(config.getAIDomain() + private Request getCancelRequest() { + URI uri = + URI.create( + config.getAIDomain() + UriResourceLocatorStateful.runCancelUri(threadId, runResponse.getId())); - log.debug("ChatGPT Run Cancel request URI: {}", uri); + log.debug("ChatGPT Run Cancel request URI: {}", uri); - return httpClient.createRequestFromJson(uri.toString(), config, new Object()); - } + return httpClient.createRequestFromJson(uri.toString(), config, new Object()); + } - private Request getRunPollRequest(URI uri) { - return httpClient.createRequestFromJson(uri.toString(), config, null); - } + private Request getRunPollRequest(URI uri) { + return httpClient.createRequestFromJson(uri.toString(), config, null); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThread.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThread.java index c8740ca..e331972 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThread.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThread.java
@@ -1,54 +1,50 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptResponse; +import java.net.URI; import lombok.extern.slf4j.Slf4j; import okhttp3.Request; -import java.net.URI; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; - @Slf4j public class ChatGptThread { - public static final String KEY_THREAD_ID = "threadId"; + public static final String KEY_THREAD_ID = "threadId"; - private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); - private final Configuration config; - private final PluginDataHandler changeDataHandler; + private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); + private final Configuration config; + private final PluginDataHandler changeDataHandler; - public ChatGptThread( - Configuration config, - PluginDataHandlerProvider pluginDataHandlerProvider - ) { - this.config = config; - this.changeDataHandler = pluginDataHandlerProvider.getChangeScope(); + public ChatGptThread(Configuration config, PluginDataHandlerProvider pluginDataHandlerProvider) { + this.config = config; + this.changeDataHandler = pluginDataHandlerProvider.getChangeScope(); + } + + public String createThread() { + String threadId = changeDataHandler.getValue(KEY_THREAD_ID); + if (threadId == null) { + Request request = createThreadRequest(); + log.debug("ChatGPT Create Thread request: {}", request); + + ChatGptResponse threadResponse = + getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); + log.info("Thread created: {}", threadResponse); + threadId = threadResponse.getId(); + changeDataHandler.setValue(KEY_THREAD_ID, threadId); + } else { + log.info("Thread found for the Change Set. Thread ID: {}", threadId); } + return threadId; + } - public String createThread() { - String threadId = changeDataHandler.getValue(KEY_THREAD_ID); - if (threadId == null) { - Request request = createThreadRequest(); - log.debug("ChatGPT Create Thread request: {}", request); + private Request createThreadRequest() { + URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.threadsUri()); + log.debug("ChatGPT Create Thread request URI: {}", uri); - ChatGptResponse threadResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); - log.info("Thread created: {}", threadResponse); - threadId = threadResponse.getId(); - changeDataHandler.setValue(KEY_THREAD_ID, threadId); - } - else { - log.info("Thread found for the Change Set. Thread ID: {}", threadId); - } - return threadId; - } - - private Request createThreadRequest() { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.threadsUri()); - log.debug("ChatGPT Create Thread request URI: {}", uri); - - return httpClient.createRequestFromJson(uri.toString(), config, new Object()); - } + return httpClient.createRequestFromJson(uri.toString(), config, new Object()); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThreadMessage.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThreadMessage.java index fbfd3a6..98912e1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThreadMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptThreadMessage.java
@@ -1,5 +1,8 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; +import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatPromptFactory.getAIChatPromptStateful; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; @@ -9,83 +12,83 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptResponse; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptThreadMessageResponse; +import java.net.URI; import lombok.extern.slf4j.Slf4j; import okhttp3.Request; -import java.net.URI; - -import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatPromptFactory.getAIChatPromptStateful; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; - @Slf4j public class ChatGptThreadMessage extends ClientBase { - private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); - private final String threadId; + private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); + private final String threadId; - private ChangeSetData changeSetData; - private GerritChange change; - private String patchSet; - private AIChatRequestMessage addMessageRequestBody; + private ChangeSetData changeSetData; + private GerritChange change; + private String patchSet; + private AIChatRequestMessage addMessageRequestBody; - public ChatGptThreadMessage(String threadId, Configuration config) { - super(config); - this.threadId = threadId; - } + public ChatGptThreadMessage(String threadId, Configuration config) { + super(config); + this.threadId = threadId; + } - public ChatGptThreadMessage( - String threadId, - Configuration config, - ChangeSetData changeSetData, - GerritChange change, - String patchSet - ) { - this(threadId, config); - this.changeSetData = changeSetData; - this.change = change; - this.patchSet = patchSet; - } + public ChatGptThreadMessage( + String threadId, + Configuration config, + ChangeSetData changeSetData, + GerritChange change, + String patchSet) { + this(threadId, config); + this.changeSetData = changeSetData; + this.change = change; + this.patchSet = patchSet; + } - public ChatGptThreadMessageResponse retrieveMessage(String messageId) { - Request request = createRetrieveMessageRequest(messageId); - log.debug("ChatGPT Retrieve Thread Message request: {}", request); - ChatGptThreadMessageResponse threadMessageResponse = getGson().fromJson( - httpClient.execute(request), ChatGptThreadMessageResponse.class - ); - log.info("Thread Message retrieved: {}", threadMessageResponse); + public ChatGptThreadMessageResponse retrieveMessage(String messageId) { + Request request = createRetrieveMessageRequest(messageId); + log.debug("ChatGPT Retrieve Thread Message request: {}", request); + ChatGptThreadMessageResponse threadMessageResponse = + getGson().fromJson(httpClient.execute(request), ChatGptThreadMessageResponse.class); + log.info("Thread Message retrieved: {}", threadMessageResponse); - return threadMessageResponse; - } + return threadMessageResponse; + } - public void addMessage() { - Request request = addMessageRequest(); - log.debug("ChatGPT Add Message request: {}", request); + public void addMessage() { + Request request = addMessageRequest(); + log.debug("ChatGPT Add Message request: {}", request); - ChatGptResponse addMessageResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); - log.info("Message added: {}", addMessageResponse); - } + ChatGptResponse addMessageResponse = + getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); + log.info("Message added: {}", addMessageResponse); + } - public String getAddMessageRequestBody() { - return getGson().toJson(addMessageRequestBody); - } + public String getAddMessageRequestBody() { + return getGson().toJson(addMessageRequestBody); + } - private Request createRetrieveMessageRequest(String messageId) { - URI uri = URI.create(config.getAIDomain() + - UriResourceLocatorStateful.threadMessageRetrieveUri(threadId, messageId)); - log.debug("ChatGPT Retrieve Thread Message request URI: {}", uri); + private Request createRetrieveMessageRequest(String messageId) { + URI uri = + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.threadMessageRetrieveUri(threadId, messageId)); + log.debug("ChatGPT Retrieve Thread Message request URI: {}", uri); - return httpClient.createRequestFromJson(uri.toString(), config, null); - } + return httpClient.createRequestFromJson(uri.toString(), config, null); + } - private Request addMessageRequest() { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.threadMessagesUri(threadId)); - log.debug("ChatGPT Add Message request URI: {}", uri); - ChatGptPromptStateful chatGptPromptStateful = getAIChatPromptStateful(config, changeSetData, change); - addMessageRequestBody = AIChatRequestMessage.builder() - .role("user") - .content(chatGptPromptStateful.getDefaultGptThreadReviewMessage(patchSet)) - .build(); - log.debug("ChatGPT Add Message request body: {}", addMessageRequestBody); + private Request addMessageRequest() { + URI uri = + URI.create(config.getAIDomain() + UriResourceLocatorStateful.threadMessagesUri(threadId)); + log.debug("ChatGPT Add Message request URI: {}", uri); + ChatGptPromptStateful chatGptPromptStateful = + getAIChatPromptStateful(config, changeSetData, change); + addMessageRequestBody = + AIChatRequestMessage.builder() + .role("user") + .content(chatGptPromptStateful.getDefaultGptThreadReviewMessage(patchSet)) + .build(); + log.debug("ChatGPT Add Message request body: {}", addMessageRequestBody); - return httpClient.createRequestFromJson(uri.toString(), config, addMessageRequestBody); - } + return httpClient.createRequestFromJson(uri.toString(), config, addMessageRequestBody); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java index a4be290..5a13fc6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/chatgpt/ChatGptVectorStore.java
@@ -1,51 +1,53 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.ClientBase; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; -import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.*; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptCreateVectorStoreRequest; +import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptResponse; +import java.net.URI; import lombok.extern.slf4j.Slf4j; import okhttp3.Request; -import java.net.URI; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; - @Slf4j public class ChatGptVectorStore extends ClientBase { - public static final String KEY_VECTOR_STORE_ID = "vectorStoreId"; + public static final String KEY_VECTOR_STORE_ID = "vectorStoreId"; - private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); - private final String fileId; - private final GerritChange change; + private final ChatGptHttpClient httpClient = new ChatGptHttpClient(); + private final String fileId; + private final GerritChange change; - public ChatGptVectorStore(String fileId, Configuration config, GerritChange change) { - super(config); - this.fileId = fileId; - this.change = change; - } + public ChatGptVectorStore(String fileId, Configuration config, GerritChange change) { + super(config); + this.fileId = fileId; + this.change = change; + } - public ChatGptResponse createVectorStore() { - Request request = vectorStoreCreateRequest(); - log.debug("ChatGPT Create Vector Store request: {}", request); + public ChatGptResponse createVectorStore() { + Request request = vectorStoreCreateRequest(); + log.debug("ChatGPT Create Vector Store request: {}", request); - ChatGptResponse createVectorStoreResponse = getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); - log.info("Vector Store created: {}", createVectorStoreResponse); + ChatGptResponse createVectorStoreResponse = + getGson().fromJson(httpClient.execute(request), ChatGptResponse.class); + log.info("Vector Store created: {}", createVectorStoreResponse); - return createVectorStoreResponse; - } + return createVectorStoreResponse; + } - private Request vectorStoreCreateRequest() { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.vectorStoreCreateUri()); - log.debug("ChatGPT Create Vector Store request URI: {}", uri); + private Request vectorStoreCreateRequest() { + URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateful.vectorStoreCreateUri()); + log.debug("ChatGPT Create Vector Store request URI: {}", uri); - ChatGptCreateVectorStoreRequest requestBody = ChatGptCreateVectorStoreRequest.builder() - .name(change.getProjectName()) - .fileIds(new String[] { fileId }) - .build(); + ChatGptCreateVectorStoreRequest requestBody = + ChatGptCreateVectorStoreRequest.builder() + .name(change.getProjectName()) + .fileIds(new String[] {fileId}) + .build(); - log.debug("ChatGPT Create Vector Store request body: {}", requestBody); - return httpClient.createRequestFromJson(uri.toString(), config, requestBody); - } + log.debug("ChatGPT Create Vector Store request body: {}", requestBody); + return httpClient.createRequestFromJson(uri.toString(), config, requestBody); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetHelper.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetHelper.java index 87d0184..7591257 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetHelper.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetHelper.java
@@ -1,46 +1,50 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; -import lombok.extern.slf4j.Slf4j; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.COMMIT_MESSAGE_FILTER_OUT_PREFIXES; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_COMMIT_MESSAGE_PREFIX; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; - -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.COMMIT_MESSAGE_FILTER_OUT_PREFIXES; -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_COMMIT_MESSAGE_PREFIX; +import lombok.extern.slf4j.Slf4j; @Slf4j public class GerritClientPatchSetHelper { - private static final Pattern EXTRACT_B_FILENAMES_FROM_PATCH_SET = Pattern.compile("^diff --git .*? b/(.*)$", - Pattern.MULTILINE); + private static final Pattern EXTRACT_B_FILENAMES_FROM_PATCH_SET = + Pattern.compile("^diff --git .*? b/(.*)$", Pattern.MULTILINE); - public static String filterPatchWithCommitMessage(String formattedPatch) { - // Remove Patch heading up to the Date annotation, so that the commit message is included. Additionally, remove - // the change type between brackets - Pattern CONFIG_ID_HEADING_PATTERN = Pattern.compile( - "^.*?" + GERRIT_COMMIT_MESSAGE_PREFIX + "(?:\\[[^\\]]+\\] )?", - Pattern.DOTALL - ); - return CONFIG_ID_HEADING_PATTERN.matcher(formattedPatch).replaceAll(GERRIT_COMMIT_MESSAGE_PREFIX); - } + public static String filterPatchWithCommitMessage(String formattedPatch) { + // Remove Patch heading up to the Date annotation, so that the commit message is included. + // Additionally, remove + // the change type between brackets + Pattern CONFIG_ID_HEADING_PATTERN = + Pattern.compile( + "^.*?" + GERRIT_COMMIT_MESSAGE_PREFIX + "(?:\\[[^\\]]+\\] )?", Pattern.DOTALL); + return CONFIG_ID_HEADING_PATTERN + .matcher(formattedPatch) + .replaceAll(GERRIT_COMMIT_MESSAGE_PREFIX); + } - public static String filterPatchWithoutCommitMessage(GerritChange change, String formattedPatch) { - // Remove Patch heading up to the Change-Id annotation - Pattern CONFIG_ID_HEADING_PATTERN = Pattern.compile( - "^.*?" + COMMIT_MESSAGE_FILTER_OUT_PREFIXES.get("CHANGE_ID") + " " + change.getChangeKey().get(), - Pattern.DOTALL - ); - return CONFIG_ID_HEADING_PATTERN.matcher(formattedPatch).replaceAll(""); - } + public static String filterPatchWithoutCommitMessage(GerritChange change, String formattedPatch) { + // Remove Patch heading up to the Change-Id annotation + Pattern CONFIG_ID_HEADING_PATTERN = + Pattern.compile( + "^.*?" + + COMMIT_MESSAGE_FILTER_OUT_PREFIXES.get("CHANGE_ID") + + " " + + change.getChangeKey().get(), + Pattern.DOTALL); + return CONFIG_ID_HEADING_PATTERN.matcher(formattedPatch).replaceAll(""); + } - public static List<String> extractFilesFromPatch(String formattedPatch) { - Matcher extractFilenameMatcher = EXTRACT_B_FILENAMES_FROM_PATCH_SET.matcher(formattedPatch); - List<String> files = new ArrayList<>(); - while (extractFilenameMatcher.find()) { - files.add(extractFilenameMatcher.group(1)); - } - return files; + public static List<String> extractFilesFromPatch(String formattedPatch) { + Matcher extractFilenameMatcher = EXTRACT_B_FILENAMES_FROM_PATCH_SET.matcher(formattedPatch); + List<String> files = new ArrayList<>(); + while (extractFilenameMatcher.find()) { + files.add(extractFilenameMatcher.group(1)); } + return files; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java index 13af663..545ccf8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
@@ -1,64 +1,68 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit; +import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit.GerritClientPatchSetHelper.extractFilesFromPatch; +import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit.GerritClientPatchSetHelper.filterPatchWithCommitMessage; +import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit.GerritClientPatchSetHelper.filterPatchWithoutCommitMessage; + import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.util.ManualRequestContext; import com.google.inject.Inject; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.gerrit.GerritClientPatchSet; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; +import java.util.List; import lombok.extern.slf4j.Slf4j; -import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.gerrit.GerritClientPatchSetHelper.*; - @Slf4j -public class GerritClientPatchSetStateful extends com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientPatchSet implements GerritClientPatchSet { - private GerritChange change; +public class GerritClientPatchSetStateful + extends com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit + .GerritClientPatchSet + implements GerritClientPatchSet { + private GerritChange change; - @VisibleForTesting - @Inject - public GerritClientPatchSetStateful(Configuration config, AccountCache accountCache) { - super(config, accountCache); + @VisibleForTesting + @Inject + public GerritClientPatchSetStateful(Configuration config, AccountCache accountCache) { + super(config, accountCache); + } + + public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception { + if (change.getIsCommentEvent()) return ""; + this.change = change; + + String formattedPatch = getPatchFromGerrit(); + List<String> files = extractFilesFromPatch(formattedPatch); + retrieveFileDiff(change, files, revisionBase); + + return formattedPatch; + } + + private String getPatchFromGerrit() throws Exception { + try (ManualRequestContext requestContext = config.openRequestContext()) { + String formattedPatch = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .current() + .patch() + .asString(); + log.debug("Formatted Patch retrieved: {}", formattedPatch); + + return filterPatch(formattedPatch); } + } - public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception { - if (change.getIsCommentEvent()) return ""; - this.change = change; - - String formattedPatch = getPatchFromGerrit(); - List<String> files = extractFilesFromPatch(formattedPatch); - retrieveFileDiff(change, files, revisionBase); - - return formattedPatch; + private String filterPatch(String formattedPatch) { + if (config.getAIReviewCommitMessages()) { + return filterPatchWithCommitMessage(formattedPatch); + } else { + return filterPatchWithoutCommitMessage(change, formattedPatch); } - - private String getPatchFromGerrit() throws Exception { - try (ManualRequestContext requestContext = config.openRequestContext()) { - String formattedPatch = config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .current() - .patch() - .asString(); - log.debug("Formatted Patch retrieved: {}", formattedPatch); - - return filterPatch(formattedPatch); - } - } - - private String filterPatch(String formattedPatch) { - if (config.getAIReviewCommitMessages()) { - return filterPatchWithCommitMessage(formattedPatch); - } - else { - return filterPatchWithoutCommitMessage(change, formattedPatch); - } - } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/git/GitRepoFiles.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/git/GitRepoFiles.java index b618a43..4af9c48 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/git/GitRepoFiles.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/api/git/GitRepoFiles.java
@@ -1,80 +1,86 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git; +import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.matchesExtensionList; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.lib.*; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.TreeFilter; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils.matchesExtensionList; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; @Slf4j public class GitRepoFiles { - public static final String REPO_PATTERN = "git/%s.git"; + public static final String REPO_PATTERN = "git/%s.git"; - private List<String> enabledFileExtensions; + private List<String> enabledFileExtensions; - public String getGitRepoFiles(Configuration config, GerritChange change) { - enabledFileExtensions = config.getEnabledFileExtensions(); - log.debug("Open Repo from {}", change.getProjectNameKey()); - String repoPath = String.format(REPO_PATTERN, change.getProjectNameKey().toString()); - try { - Repository repository = openRepository(repoPath); - log.debug("Open Repo path {}", repoPath); - Map<String, String> filesWithContent = listFilesWithContent(repository); + public String getGitRepoFiles(Configuration config, GerritChange change) { + enabledFileExtensions = config.getEnabledFileExtensions(); + log.debug("Open Repo from {}", change.getProjectNameKey()); + String repoPath = String.format(REPO_PATTERN, change.getProjectNameKey().toString()); + try { + Repository repository = openRepository(repoPath); + log.debug("Open Repo path {}", repoPath); + Map<String, String> filesWithContent = listFilesWithContent(repository); - return getGson().toJson(filesWithContent); - } catch (IOException | GitAPIException e) { - throw new RuntimeException("Failed to retrieve files in master branch: ", e); + return getGson().toJson(filesWithContent); + } catch (IOException | GitAPIException e) { + throw new RuntimeException("Failed to retrieve files in master branch: ", e); + } + } + + public Map<String, String> listFilesWithContent(Repository repository) + throws IOException, GitAPIException { + Map<String, String> filesWithContent = new HashMap<>(); + try (ObjectReader reader = repository.newObjectReader(); + RevWalk revWalk = new RevWalk(repository)) { + ObjectId lastCommitId = repository.resolve(Constants.R_HEADS + "master"); + RevCommit commit = revWalk.parseCommit(lastCommitId); + RevTree tree = commit.getTree(); + + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(tree); + treeWalk.setRecursive(true); + treeWalk.setFilter(TreeFilter.ANY_DIFF); + + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + if (!matchesExtensionList(path, enabledFileExtensions)) continue; + ObjectId objectId = treeWalk.getObjectId(0); + byte[] bytes = reader.open(objectId).getBytes(); + String content = + new String(bytes, StandardCharsets.UTF_8); // Assumes text files with UTF-8 encoding + log.debug("Repo File loaded: {}", path); + filesWithContent.put(path, content); } + } } + return filesWithContent; + } - public Map<String, String> listFilesWithContent(Repository repository) throws IOException, GitAPIException { - Map<String, String> filesWithContent = new HashMap<>(); - try (ObjectReader reader = repository.newObjectReader(); - RevWalk revWalk = new RevWalk(repository)) { - ObjectId lastCommitId = repository.resolve(Constants.R_HEADS + "master"); - RevCommit commit = revWalk.parseCommit(lastCommitId); - RevTree tree = commit.getTree(); - - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(tree); - treeWalk.setRecursive(true); - treeWalk.setFilter(TreeFilter.ANY_DIFF); - - while (treeWalk.next()) { - String path = treeWalk.getPathString(); - if (!matchesExtensionList(path, enabledFileExtensions)) continue; - ObjectId objectId = treeWalk.getObjectId(0); - byte[] bytes = reader.open(objectId).getBytes(); - String content = new String(bytes, StandardCharsets.UTF_8); // Assumes text files with UTF-8 encoding - log.debug("Repo File loaded: {}", path); - filesWithContent.put(path, content); - } - } - } - return filesWithContent; - } - - public Repository openRepository(String path) throws IOException { - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - return builder.setGitDir(new File(path)) - .readEnvironment() - .findGitDir() - .setMustExist(true) - .build(); - } + public Repository openRepository(String path) throws IOException { + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + return builder + .setGitDir(new File(path)) + .readEnvironment() + .findGitDir() + .setMustExist(true) + .build(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatDataPromptRequestsStateful.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatDataPromptRequestsStateful.java index 25b4353..2da30dd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatDataPromptRequestsStateful.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatDataPromptRequestsStateful.java
@@ -7,13 +7,13 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.GerritClientData; -public class AIChatDataPromptRequestsStateful extends AIChatDataPromptRequests implements ChatAIDataPrompt { - public AIChatDataPromptRequestsStateful( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - super(config, changeSetData, gerritClientData, localizer); - } +public class AIChatDataPromptRequestsStateful extends AIChatDataPromptRequests + implements ChatAIDataPrompt { + public AIChatDataPromptRequestsStateful( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + super(config, changeSetData, gerritClientData, localizer); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulBase.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulBase.java index d1df98b..3d4db26 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulBase.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulBase.java
@@ -1,65 +1,67 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.DOT; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithSpace; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatGptPrompt; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; +import lombok.extern.slf4j.Slf4j; @Slf4j -public abstract class AIChatGptPromptStatefulBase extends AIChatGptPrompt implements ChatGptPromptStateful { - public static String DEFAULT_AI_CHAT_ASSISTANT_NAME; - public static String DEFAULT_AI_CHAT_ASSISTANT_DESCRIPTION; - public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS; - public static String DEFAULT_AI_CHAT_MESSAGE_REVIEW; +public abstract class AIChatGptPromptStatefulBase extends AIChatGptPrompt + implements ChatGptPromptStateful { + public static String DEFAULT_AI_CHAT_ASSISTANT_NAME; + public static String DEFAULT_AI_CHAT_ASSISTANT_DESCRIPTION; + public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS; + public static String DEFAULT_AI_CHAT_MESSAGE_REVIEW; - protected final ChangeSetData changeSetData; + protected final ChangeSetData changeSetData; - private final GerritChange change; + private final GerritChange change; - public AIChatGptPromptStatefulBase(Configuration config, ChangeSetData changeSetData, GerritChange change) { - super(config); - this.changeSetData = changeSetData; - this.change = change; - this.isCommentEvent = change.getIsCommentEvent(); - // Avoid repeated loading of prompt constants - if (DEFAULT_AI_CHAT_ASSISTANT_NAME == null) { - loadDefaultPrompts("promptsStateful"); - } + public AIChatGptPromptStatefulBase( + Configuration config, ChangeSetData changeSetData, GerritChange change) { + super(config); + this.changeSetData = changeSetData; + this.change = change; + this.isCommentEvent = change.getIsCommentEvent(); + // Avoid repeated loading of prompt constants + if (DEFAULT_AI_CHAT_ASSISTANT_NAME == null) { + loadDefaultPrompts("promptsStateful"); } + } - public String getDefaultGptAssistantDescription() { - return String.format(DEFAULT_AI_CHAT_ASSISTANT_DESCRIPTION, change.getProjectName()); - } + public String getDefaultGptAssistantDescription() { + return String.format(DEFAULT_AI_CHAT_ASSISTANT_DESCRIPTION, change.getProjectName()); + } - public abstract void addGptAssistantInstructions(List<String> instructions); + public abstract void addGptAssistantInstructions(List<String> instructions); - public abstract String getAIRequestDataPrompt(); + public abstract String getAIRequestDataPrompt(); - public String getDefaultGptAssistantInstructions() { - List<String> instructions = new ArrayList<>(List.of( + public String getDefaultGptAssistantInstructions() { + List<String> instructions = + new ArrayList<>( + List.of( DEFAULT_AI_CHAT_SYSTEM_PROMPT + DOT, - String.format(DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS, change.getProjectName()) - )); - addGptAssistantInstructions(instructions); + String.format(DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS, change.getProjectName()))); + addGptAssistantInstructions(instructions); - return joinWithSpace(instructions); - } + return joinWithSpace(instructions); + } - public String getDefaultGptThreadReviewMessage(String patchSet) { - String gptRequestDataPrompt = getAIRequestDataPrompt(); - if (gptRequestDataPrompt != null && !gptRequestDataPrompt.isEmpty()) { - log.debug("Request User Prompt retrieved: {}", gptRequestDataPrompt); - return gptRequestDataPrompt; - } - else { - return String.format(DEFAULT_AI_CHAT_MESSAGE_REVIEW, patchSet); - } + public String getDefaultGptThreadReviewMessage(String patchSet) { + String gptRequestDataPrompt = getAIRequestDataPrompt(); + if (gptRequestDataPrompt != null && !gptRequestDataPrompt.isEmpty()) { + log.debug("Request User Prompt retrieved: {}", gptRequestDataPrompt); + return gptRequestDataPrompt; + } else { + return String.format(DEFAULT_AI_CHAT_MESSAGE_REVIEW, patchSet); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulRequests.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulRequests.java index 9ccbbbd..9664ea5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulRequests.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulRequests.java
@@ -4,32 +4,33 @@ import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; +import java.util.List; import lombok.extern.slf4j.Slf4j; -import java.util.List; - @Slf4j -public class AIChatGptPromptStatefulRequests extends AIChatGptPromptStatefulBase implements ChatGptPromptStateful { - public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS; +public class AIChatGptPromptStatefulRequests extends AIChatGptPromptStatefulBase + implements ChatGptPromptStateful { + public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS; - public AIChatGptPromptStatefulRequests(Configuration config, ChangeSetData changeSetData, GerritChange change) { - super(config, changeSetData, change); - if (DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS == null) { - loadDefaultPrompts("promptsStatefulRequests"); - } + public AIChatGptPromptStatefulRequests( + Configuration config, ChangeSetData changeSetData, GerritChange change) { + super(config, changeSetData, change); + if (DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS == null) { + loadDefaultPrompts("promptsStatefulRequests"); } + } - @Override - public void addGptAssistantInstructions(List<String> instructions) { - instructions.addAll(List.of( - DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS, - getCommentRequestPrompt(changeSetData.getCommentPropertiesSize()) - )); - } + @Override + public void addGptAssistantInstructions(List<String> instructions) { + instructions.addAll( + List.of( + DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REQUESTS, + getCommentRequestPrompt(changeSetData.getCommentPropertiesSize()))); + } - @Override - public String getAIRequestDataPrompt() { - if (changeSetData == null ) return null; - return changeSetData.getReviewAIDataPrompt(); - } + @Override + public String getAIRequestDataPrompt() { + if (changeSetData == null) return null; + return changeSetData.getReviewAIDataPrompt(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulReview.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulReview.java index 8f006d6..95fbfad 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulReview.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/client/prompt/AIChatGptPromptStatefulReview.java
@@ -1,55 +1,58 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.COLON_SPACE; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.getNumberedList; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; +import lombok.extern.slf4j.Slf4j; @Slf4j -public class AIChatGptPromptStatefulReview extends AIChatGptPromptStatefulBase implements ChatGptPromptStateful { - private static final String RULE_NUMBER_PREFIX = "RULE #"; +public class AIChatGptPromptStatefulReview extends AIChatGptPromptStatefulBase + implements ChatGptPromptStateful { + private static final String RULE_NUMBER_PREFIX = "RULE #"; - public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW; - public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_DONT_GUESS_CODE; - public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_HISTORY; + public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW; + public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_DONT_GUESS_CODE; + public static String DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_HISTORY; - public AIChatGptPromptStatefulReview(Configuration config, ChangeSetData changeSetData, GerritChange change) { - super(config, changeSetData, change); - if (DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW == null) { - loadDefaultPrompts("promptsStatefulReview"); - } + public AIChatGptPromptStatefulReview( + Configuration config, ChangeSetData changeSetData, GerritChange change) { + super(config, changeSetData, change); + if (DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW == null) { + loadDefaultPrompts("promptsStatefulReview"); } + } - @Override - public void addGptAssistantInstructions(List<String> instructions) { - instructions.addAll(List.of( - getGptAssistantInstructionsReview(), - getPatchSetReviewPrompt() - )); - if (config.getAIReviewCommitMessages()) { - instructions.add(getReviewPromptCommitMessages()); - } + @Override + public void addGptAssistantInstructions(List<String> instructions) { + instructions.addAll(List.of(getGptAssistantInstructionsReview(), getPatchSetReviewPrompt())); + if (config.getAIReviewCommitMessages()) { + instructions.add(getReviewPromptCommitMessages()); } + } - @Override - public String getAIRequestDataPrompt() { - return null; - } + @Override + public String getAIRequestDataPrompt() { + return null; + } - private String getGptAssistantInstructionsReview() { - return String.format(DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW, joinWithNewLine(getNumberedList( - new ArrayList<>(List.of( + private String getGptAssistantInstructionsReview() { + return String.format( + DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_REVIEW, + joinWithNewLine( + getNumberedList( + new ArrayList<>( + List.of( DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT, DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_DONT_GUESS_CODE, - DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_HISTORY - )), - RULE_NUMBER_PREFIX, COLON_SPACE - ))); - } + DEFAULT_AI_CHAT_ASSISTANT_INSTRUCTIONS_HISTORY)), + RULE_NUMBER_PREFIX, + COLON_SPACE))); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateAssistantRequestBody.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateAssistantRequestBody.java index 94c0df4..7bb7954 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateAssistantRequestBody.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateAssistantRequestBody.java
@@ -8,12 +8,13 @@ @Data @Builder public class ChatGptCreateAssistantRequestBody { - private String name; - private String description; - private String instructions; - private String model; - private Double temperature; - private AIChatTool[] tools; - @SerializedName("tool_resources") - private ChatGptToolResources toolResources; + private String name; + private String description; + private String instructions; + private String model; + private Double temperature; + private AIChatTool[] tools; + + @SerializedName("tool_resources") + private ChatGptToolResources toolResources; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateRunRequest.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateRunRequest.java index 0a0aae0..61340a0 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateRunRequest.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateRunRequest.java
@@ -7,6 +7,6 @@ @Data @Builder public class ChatGptCreateRunRequest { - @SerializedName("assistant_id") - private String assistantId; + @SerializedName("assistant_id") + private String assistantId; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java index b755054..12709b9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptCreateVectorStoreRequest.java
@@ -7,7 +7,8 @@ @Data @Builder public class ChatGptCreateVectorStoreRequest { - private String name; - @SerializedName("file_ids") - private String[] fileIds; + private String name; + + @SerializedName("file_ids") + private String[] fileIds; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptFilesResponse.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptFilesResponse.java index 88d2cb6..7921a06 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptFilesResponse.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptFilesResponse.java
@@ -6,5 +6,5 @@ @EqualsAndHashCode(callSuper = true) @Data public class ChatGptFilesResponse extends ChatGptResponse { - private String filename; + private String filename; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptListResponse.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptListResponse.java index 14363a7..70aa843 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptListResponse.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptListResponse.java
@@ -1,11 +1,10 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt; -import lombok.Data; - import java.util.List; +import lombok.Data; @Data public class ChatGptListResponse { - private String object; - private List<ChatGptRunStepsResponse> data; + private String object; + private List<ChatGptRunStepsResponse> data; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptResponse.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptResponse.java index 84d5ae5..a8ba169 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptResponse.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptResponse.java
@@ -4,7 +4,7 @@ @Data public class ChatGptResponse { - private String id; - private String object; - private String status; + private String id; + private String object; + private String status; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptRunStepsResponse.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptRunStepsResponse.java index b70847a..9538fe8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptRunStepsResponse.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptRunStepsResponse.java
@@ -8,6 +8,6 @@ @EqualsAndHashCode(callSuper = true) @Data public class ChatGptRunStepsResponse extends ChatGptResponse { - @SerializedName("step_details") - private AIChatResponseMessage stepDetails; + @SerializedName("step_details") + private AIChatResponseMessage stepDetails; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptThreadMessageResponse.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptThreadMessageResponse.java index 6c61254..938a2e1 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptThreadMessageResponse.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptThreadMessageResponse.java
@@ -1,23 +1,22 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - @EqualsAndHashCode(callSuper = true) @Data public class ChatGptThreadMessageResponse extends ChatGptResponse { - private List<Content> content; + private List<Content> content; + + @Data + public static class Content { + private String type; + private Text text; @Data - public static class Content { - private String type; - private Text text; - - @Data - public static class Text { - private String value; - } + public static class Text { + private String value; } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptToolResources.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptToolResources.java index fcf268c..76dfefe 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptToolResources.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateful/model/api/chatgpt/ChatGptToolResources.java
@@ -7,13 +7,13 @@ @Data @AllArgsConstructor public class ChatGptToolResources { - @SerializedName("file_search") - private VectorStoreIds fileSearch; + @SerializedName("file_search") + private VectorStoreIds fileSearch; - @Data - @AllArgsConstructor - public static class VectorStoreIds { - @SerializedName("vector_store_ids") - private String[] vectorStoreIds; - } -} \ No newline at end of file + @Data + @AllArgsConstructor + public static class VectorStoreIds { + @SerializedName("vector_store_ids") + private String[] vectorStoreIds; + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/UriResourceLocatorStateless.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/UriResourceLocatorStateless.java index 1475dfc..d9e7723 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/UriResourceLocatorStateless.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/UriResourceLocatorStateless.java
@@ -1,44 +1,48 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api; - import com.google.common.base.Strings; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; public class UriResourceLocatorStateless { - public static String getChatResourceUri(Configuration configuration) { - // different resource Uri endpoints exist for each aiType we support. - // some have backward compatible endpoints, but some do not. This abstracts the knowledge - // of which to call for which aiType set in the configuration settings. - switch (configuration.getAIType()) { - case CHATGPT: - // ollama has an OpenAI compatible completions URI endpoint now, - // use so that the tools_calls format is used for returned data by default saving - // on loads more code changes in the response parsing. - case OLLAMA: - return chatCompletionsUri(); + public static String getChatResourceUri(Configuration configuration) { + // different resource Uri endpoints exist for each aiType we support. + // some have backward compatible endpoints, but some do not. This abstracts the knowledge + // of which to call for which aiType set in the configuration settings. + switch (configuration.getAIType()) { + case CHATGPT: + // ollama has an OpenAI compatible completions URI endpoint now, + // use so that the tools_calls format is used for returned data by default saving + // on loads more code changes in the response parsing. + case OLLAMA: + return chatCompletionsUri(); - case AZUREOPENAI: - // TODO: add the endpoint information here, along with the additional query param with the version - // info - for testing use generic for now, but we will want to add the additional version info as another - // config element and then append the 2 together here. - throw new UnsupportedOperationException("AzureOpenAi endpoint not yet supported."); - case GENERIC: - // generic ai development will require you to override the endpoint if it doesn't support the existing - // chatCompletionsUri.. Usually you will provide the optional chatEndpoint configuration option. - final String chatEndpoint = configuration.getChatEndpoint(); - // fallback onto chatCompletions api if nothing has been specified. - return Strings.isNullOrEmpty(chatEndpoint) ? chatCompletionsUri() : chatEndpoint; - default: - throw new UnsupportedOperationException("Unsupported aiType, chat resource endpoint not yet described."); - } + case AZUREOPENAI: + // TODO: add the endpoint information here, along with the additional query param with the + // version + // info - for testing use generic for now, but we will want to add the additional version + // info as another + // config element and then append the 2 together here. + throw new UnsupportedOperationException("AzureOpenAi endpoint not yet supported."); + case GENERIC: + // generic ai development will require you to override the endpoint if it doesn't support + // the existing + // chatCompletionsUri.. Usually you will provide the optional chatEndpoint configuration + // option. + final String chatEndpoint = configuration.getChatEndpoint(); + // fallback onto chatCompletions api if nothing has been specified. + return Strings.isNullOrEmpty(chatEndpoint) ? chatCompletionsUri() : chatEndpoint; + default: + throw new UnsupportedOperationException( + "Unsupported aiType, chat resource endpoint not yet described."); } + } - public static String chatCompletionsUri() { - return "/v1/chat/completions"; - } + public static String chatCompletionsUri() { + return "/v1/chat/completions"; + } - public static String ollamaChatUri() { - return "/api/chat"; - } + public static String ollamaChatUri() { + return "/api/chat"; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/chatai/AIChatClientStateless.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/chatai/AIChatClientStateless.java index 36adbe5..51b357f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/chatai/AIChatClientStateless.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/chatai/AIChatClientStateless.java
@@ -1,117 +1,127 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.chatai; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getNoEscapedGson; + import com.google.common.annotations.VisibleForTesting; import com.google.common.net.HttpHeaders; import com.google.inject.Inject; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi.ChatAIClient; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatClient; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatParameters; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai.AIChatTools; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.http.HttpClientWithRetry; -import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.*; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; +import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatTool; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.UriResourceLocatorStateless; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.prompt.AIChatPromptStateless; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.model.api.chatgpt.ChatGptCompletionRequest; -import lombok.extern.slf4j.Slf4j; -import org.apache.http.NameValuePair; -import org.apache.http.entity.ContentType; - import java.io.IOException; import java.net.URI; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getNoEscapedGson; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.NameValuePair; +import org.apache.http.entity.ContentType; @Slf4j @Singleton public class AIChatClientStateless extends AIChatClient implements ChatAIClient { - private static final int REVIEW_ATTEMPT_LIMIT = 3; + private static final int REVIEW_ATTEMPT_LIMIT = 3; - private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry(); + private final HttpClientWithRetry httpClientWithRetry = new HttpClientWithRetry(); - @VisibleForTesting - @Inject - public AIChatClientStateless(Configuration config) { - super(config); + @VisibleForTesting + @Inject + public AIChatClientStateless(Configuration config) { + super(config); + } + + public AIChatResponseContent ask( + ChangeSetData changeSetData, GerritChange change, String patchSet) throws Exception { + isCommentEvent = change.getIsCommentEvent(); + String changeId = change.getFullChangeId(); + log.info( + "Processing STATELESS ChatGPT Request with changeId: {}, Patch Set: {}", + changeId, + patchSet); + for (int attemptInd = 0; attemptInd < REVIEW_ATTEMPT_LIMIT; attemptInd++) { + HttpRequest request = createRequest(config, changeSetData, patchSet); + log.debug("ChatGPT request: {}", request.toString()); + + HttpResponse<String> response = httpClientWithRetry.execute(request); + + String body = response.body(); + log.debug("ChatGPT response body: {}", body); + if (body == null) { + throw new IOException("ChatGPT response body is null"); + } + + AIChatResponseContent contentExtracted = extractContent(config, body); + if (validateResponse(contentExtracted, changeId, attemptInd)) { + return contentExtracted; + } } + throw new RuntimeException("Failed to receive valid ChatGPT response"); + } - public AIChatResponseContent ask(ChangeSetData changeSetData, GerritChange change, String patchSet) - throws Exception { - isCommentEvent = change.getIsCommentEvent(); - String changeId = change.getFullChangeId(); - log.info("Processing STATELESS ChatGPT Request with changeId: {}, Patch Set: {}", changeId, patchSet); - for (int attemptInd = 0; attemptInd < REVIEW_ATTEMPT_LIMIT; attemptInd++) { - HttpRequest request = createRequest(config, changeSetData, patchSet); - log.debug("ChatGPT request: {}", request.toString()); + protected HttpRequest createRequest( + Configuration config, ChangeSetData changeSetData, String patchSet) { + URI uri = + URI.create(config.getAIDomain() + UriResourceLocatorStateless.getChatResourceUri(config)); + log.debug("AIChat request URI: {}", uri); + requestBody = createRequestBody(config, changeSetData, patchSet); + log.debug("AIChat request body: {}", requestBody); - HttpResponse<String> response = httpClientWithRetry.execute(request); + HttpRequest.Builder builder = + HttpRequest.newBuilder() + .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .uri(uri) + .POST(HttpRequest.BodyPublishers.ofString(requestBody)); - String body = response.body(); - log.debug("ChatGPT response body: {}", body); - if (body == null) { - throw new IOException("ChatGPT response body is null"); - } - - AIChatResponseContent contentExtracted = extractContent(config, body); - if (validateResponse(contentExtracted, changeId, attemptInd)) { - return contentExtracted; - } - } - throw new RuntimeException("Failed to receive valid ChatGPT response"); + // depending on the aiType, add appropriate authorization header ( if required ). + NameValuePair authHeader = config.getAuthorizationHeaderInfo(); + if (authHeader != null) { + builder.header(authHeader.getName(), authHeader.getValue()); } + return builder.build(); + } - protected HttpRequest createRequest(Configuration config, ChangeSetData changeSetData, String patchSet) { - URI uri = URI.create(config.getAIDomain() + UriResourceLocatorStateless.getChatResourceUri(config)); - log.debug("AIChat request URI: {}", uri); - requestBody = createRequestBody(config, changeSetData, patchSet); - log.debug("AIChat request body: {}", requestBody); + private String createRequestBody( + Configuration config, ChangeSetData changeSetData, String patchSet) { + AIChatPromptStateless AIChatPromptStateless = new AIChatPromptStateless(config, isCommentEvent); + AIChatRequestMessage systemMessage = + AIChatRequestMessage.builder() + .role("system") + .content(AIChatPromptStateless.getAISystemPrompt()) + .build(); + AIChatRequestMessage userMessage = + AIChatRequestMessage.builder() + .role("user") + .content(AIChatPromptStateless.getGptUserPrompt(changeSetData, patchSet)) + .build(); - HttpRequest.Builder builder = HttpRequest.newBuilder() - .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .uri(uri) - .POST(HttpRequest.BodyPublishers.ofString(requestBody)); + AIChatParameters AIChatParameters = new AIChatParameters(config, isCommentEvent); + AIChatTool[] tools = new AIChatTool[] {AIChatTools.retrieveFormatRepliesTool()}; + ChatGptCompletionRequest chatGptCompletionRequest = + ChatGptCompletionRequest.builder() + .model(config.getAIModel()) + .messages(List.of(systemMessage, userMessage)) + .temperature(AIChatParameters.getGptTemperature()) + .stream(AIChatParameters.getStreamOutput()) + // Seed value is Utilized to prevent ChatGPT from mixing up separate API calls that + // occur in close + // temporal proximity. + .seed(AIChatParameters.getRandomSeed()) + .tools(tools) + .toolChoice(AIChatTools.retrieveFormatRepliesToolChoice()) + .build(); - // depending on the aiType, add appropriate authorization header ( if required ). - NameValuePair authHeader = config.getAuthorizationHeaderInfo(); - if (authHeader != null) { - builder.header(authHeader.getName(), authHeader.getValue()); - } - return builder.build(); - } - - private String createRequestBody(Configuration config, ChangeSetData changeSetData, String patchSet) { - AIChatPromptStateless AIChatPromptStateless = new AIChatPromptStateless(config, isCommentEvent); - AIChatRequestMessage systemMessage = AIChatRequestMessage.builder() - .role("system") - .content(AIChatPromptStateless.getAISystemPrompt()) - .build(); - AIChatRequestMessage userMessage = AIChatRequestMessage.builder() - .role("user") - .content(AIChatPromptStateless.getGptUserPrompt(changeSetData, patchSet)) - .build(); - - AIChatParameters AIChatParameters = new AIChatParameters(config, isCommentEvent); - AIChatTool[] tools = new AIChatTool[]{ - AIChatTools.retrieveFormatRepliesTool() - }; - ChatGptCompletionRequest chatGptCompletionRequest = ChatGptCompletionRequest.builder() - .model(config.getAIModel()) - .messages(List.of(systemMessage, userMessage)) - .temperature(AIChatParameters.getGptTemperature()) - .stream(AIChatParameters.getStreamOutput()) - // Seed value is Utilized to prevent ChatGPT from mixing up separate API calls that occur in close - // temporal proximity. - .seed(AIChatParameters.getRandomSeed()) - .tools(tools) - .toolChoice(AIChatTools.retrieveFormatRepliesToolChoice()) - .build(); - - return getNoEscapedGson().toJson(chatGptCompletionRequest); - } + return getNoEscapedGson().toJson(chatGptCompletionRequest); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java index ad8e28c..65dfc43 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
@@ -1,77 +1,81 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.gerrit; -import com.google.inject.Inject; +import static java.util.stream.Collectors.toList; + import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.extensions.common.FileInfo; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.util.ManualRequestContext; +import com.google.inject.Inject; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.gerrit.GerritClientPatchSet; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - -import static java.util.stream.Collectors.toList; - import java.util.List; import java.util.Map; +import lombok.extern.slf4j.Slf4j; @Slf4j -public class GerritClientPatchSetStateless extends com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClientPatchSet implements GerritClientPatchSet { - @VisibleForTesting - @Inject - public GerritClientPatchSetStateless(Configuration config, AccountCache accountCache) { - super(config, accountCache); +public class GerritClientPatchSetStateless + extends com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit + .GerritClientPatchSet + implements GerritClientPatchSet { + @VisibleForTesting + @Inject + public GerritClientPatchSetStateless(Configuration config, AccountCache accountCache) { + super(config, accountCache); + } + + public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception { + int revisionBase = getChangeSetRevisionBase(changeSetData); + log.debug("Revision base: {}", revisionBase); + + List<String> files = getAffectedFiles(change, revisionBase); + log.debug("Patch files: {}", files); + + String fileDiffsJson = getFileDiffsJson(change, files, revisionBase); + log.debug("File diffs: {}", fileDiffsJson); + + return fileDiffsJson; + } + + private List<String> getAffectedFiles(GerritChange change, int revisionBase) throws Exception { + try (ManualRequestContext requestContext = config.openRequestContext()) { + Map<String, FileInfo> files = + config + .getGerritApi() + .changes() + .id( + change.getProjectName(), + change.getBranchNameKey().shortName(), + change.getChangeKey().get()) + .current() + .files(revisionBase); + return files.entrySet().stream() + .filter( + fileEntry -> { + String filename = fileEntry.getKey(); + if (!filename.equals("/COMMIT_MSG") || config.getAIReviewCommitMessages()) { + if (fileEntry.getValue().size > config.getMaxReviewFileSize()) { + log.info( + "File '{}' not reviewed because its size exceeds the fixed maximum" + + " allowable size.", + filename); + } else { + return true; + } + } + return false; + }) + .map(Map.Entry::getKey) + .collect(toList()); } + } - public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception { - int revisionBase = getChangeSetRevisionBase(changeSetData); - log.debug("Revision base: {}", revisionBase); - - List<String> files = getAffectedFiles(change, revisionBase); - log.debug("Patch files: {}", files); - - String fileDiffsJson = getFileDiffsJson(change, files, revisionBase); - log.debug("File diffs: {}", fileDiffsJson); - - return fileDiffsJson; - } - - private List<String> getAffectedFiles(GerritChange change, int revisionBase) throws Exception { - try (ManualRequestContext requestContext = config.openRequestContext()) { - Map<String, FileInfo> files = - config - .getGerritApi() - .changes() - .id( - change.getProjectName(), - change.getBranchNameKey().shortName(), - change.getChangeKey().get()) - .current() - .files(revisionBase); - return files.entrySet().stream() - .filter( - fileEntry -> { - String filename = fileEntry.getKey(); - if (!filename.equals("/COMMIT_MSG") || config.getAIReviewCommitMessages()) { - if (fileEntry.getValue().size > config.getMaxReviewFileSize()) { - log.info( - "File '{}' not reviewed because its size exceeds the fixed maximum allowable size.", - filename); - } else { - return true; - } - } - return false; - }) - .map(Map.Entry::getKey) - .collect(toList()); - } - } - - private String getFileDiffsJson(GerritChange change, List<String> files, int revisionBase) throws Exception { - retrieveFileDiff(change, files, revisionBase); - diffs.add(String.format("{\"changeId\": \"%s\"}", change.getFullChangeId())); - return "[" + String.join(",", diffs) + "]\n"; - } + private String getFileDiffsJson(GerritChange change, List<String> files, int revisionBase) + throws Exception { + retrieveFileDiff(change, files, revisionBase); + diffs.add(String.format("{\"changeId\": \"%s\"}", change.getFullChangeId())); + return "[" + String.join(",", diffs) + "]\n"; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatDataPromptRequestsStateless.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatDataPromptRequestsStateless.java index 2011a3f..cf3880f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatDataPromptRequestsStateless.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatDataPromptRequestsStateless.java
@@ -10,20 +10,20 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class AIChatDataPromptRequestsStateless extends AIChatDataPromptRequests implements ChatAIDataPrompt { - public AIChatDataPromptRequestsStateless( - Configuration config, - ChangeSetData changeSetData, - GerritClientData gerritClientData, - Localizer localizer - ) { - super(config, changeSetData, gerritClientData, localizer); - } +public class AIChatDataPromptRequestsStateless extends AIChatDataPromptRequests + implements ChatAIDataPrompt { + public AIChatDataPromptRequestsStateless( + Configuration config, + ChangeSetData changeSetData, + GerritClientData gerritClientData, + Localizer localizer) { + super(config, changeSetData, gerritClientData, localizer); + } - protected AIChatMessageItem getMessageItem(int i) { - super.getMessageItem(i); - setHistory(messageItem, messageHistory); + protected AIChatMessageItem getMessageItem(int i) { + super.getMessageItem(i); + setHistory(messageItem, messageHistory); - return messageItem; - } + return messageItem; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatPromptStateless.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatPromptStateless.java index e07ac2b..5da4dd3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatPromptStateless.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/client/prompt/AIChatPromptStateless.java
@@ -1,103 +1,108 @@ package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.prompt; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; + import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatGptPrompt; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; -import lombok.extern.slf4j.Slf4j; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; - -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.*; +import lombok.extern.slf4j.Slf4j; @Slf4j public class AIChatPromptStateless extends AIChatGptPrompt { - public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION; - public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY; - public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF; + public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION; + public static String DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY; + public static String DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF; - public AIChatPromptStateless(Configuration config) { - super(config); - loadStatelessPrompts(); - } + public AIChatPromptStateless(Configuration config) { + super(config); + loadStatelessPrompts(); + } - public AIChatPromptStateless(Configuration config, boolean isCommentEvent) { - super(config, isCommentEvent); - loadStatelessPrompts(); - } + public AIChatPromptStateless(Configuration config, boolean isCommentEvent) { + super(config, isCommentEvent); + loadStatelessPrompts(); + } - public static String getDefaultGptReviewSystemPrompt() { - return joinWithSpace(new ArrayList<>(List.of( + public static String getDefaultGptReviewSystemPrompt() { + return joinWithSpace( + new ArrayList<>( + List.of( DEFAULT_AI_CHAT_SYSTEM_PROMPT + DOT, DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION, - DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW - ))); - } + DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW))); + } - public String getAISystemPrompt() { - List<String> prompt = new ArrayList<>(Arrays.asList( - config.getString(Configuration.KEY_AI_SYSTEM_PROMPT, DEFAULT_AI_CHAT_SYSTEM_PROMPT) + DOT, - AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION - )); - if (!isCommentEvent) { - prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW); - } - return joinWithSpace(prompt); + public String getAISystemPrompt() { + List<String> prompt = + new ArrayList<>( + Arrays.asList( + config.getString(Configuration.KEY_AI_SYSTEM_PROMPT, DEFAULT_AI_CHAT_SYSTEM_PROMPT) + + DOT, + AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION)); + if (!isCommentEvent) { + prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION_REVIEW); } + return joinWithSpace(prompt); + } - public String getGptUserPrompt(ChangeSetData changeSetData, String patchSet) { - List<String> prompt = new ArrayList<>(); - String gptRequestDataPrompt = changeSetData.getReviewAIDataPrompt(); - boolean isValidRequestDataPrompt = gptRequestDataPrompt != null && !gptRequestDataPrompt.isEmpty(); - if (isCommentEvent && isValidRequestDataPrompt) { - log.debug("Request User Prompt retrieved: {}", gptRequestDataPrompt); - prompt.addAll(Arrays.asList( - DEFAULT_AI_CHAT_REQUEST_PROMPT_DIFF, - patchSet, - DEFAULT_AI_CHAT_REQUEST_PROMPT_REQUESTS, - gptRequestDataPrompt, - getCommentRequestPrompt(changeSetData.getCommentPropertiesSize()) - )); - } - else { - prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT); - prompt.addAll(getReviewSteps()); - prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF); - prompt.add(patchSet); - if (isValidRequestDataPrompt) { - prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY); - prompt.add(gptRequestDataPrompt); - } - if (!changeSetData.getDirectives().isEmpty()) { - prompt.add(DEFAULT_AI_CHAT_REVIEW_PROMPT_DIRECTIVES); - prompt.add(getNumberedListString(new ArrayList<>(changeSetData.getDirectives()), null, null)); - } - } - return joinWithNewLine(prompt); + public String getGptUserPrompt(ChangeSetData changeSetData, String patchSet) { + List<String> prompt = new ArrayList<>(); + String gptRequestDataPrompt = changeSetData.getReviewAIDataPrompt(); + boolean isValidRequestDataPrompt = + gptRequestDataPrompt != null && !gptRequestDataPrompt.isEmpty(); + if (isCommentEvent && isValidRequestDataPrompt) { + log.debug("Request User Prompt retrieved: {}", gptRequestDataPrompt); + prompt.addAll( + Arrays.asList( + DEFAULT_AI_CHAT_REQUEST_PROMPT_DIFF, + patchSet, + DEFAULT_AI_CHAT_REQUEST_PROMPT_REQUESTS, + gptRequestDataPrompt, + getCommentRequestPrompt(changeSetData.getCommentPropertiesSize()))); + } else { + prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT); + prompt.addAll(getReviewSteps()); + prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF); + prompt.add(patchSet); + if (isValidRequestDataPrompt) { + prompt.add(AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY); + prompt.add(gptRequestDataPrompt); + } + if (!changeSetData.getDirectives().isEmpty()) { + prompt.add(DEFAULT_AI_CHAT_REVIEW_PROMPT_DIRECTIVES); + prompt.add( + getNumberedListString(new ArrayList<>(changeSetData.getDirectives()), null, null)); + } } + return joinWithNewLine(prompt); + } - private void loadStatelessPrompts() { - // Avoid repeated loading of prompt constants - if (DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION == null) { - loadDefaultPrompts("promptsStateless"); - } + private void loadStatelessPrompts() { + // Avoid repeated loading of prompt constants + if (DEFAULT_AI_CHAT_SYSTEM_PROMPT_INPUT_DESCRIPTION == null) { + loadDefaultPrompts("promptsStateless"); } + } - private List<String> getReviewSteps() { - List<String> steps = new ArrayList<>(List.of( - joinWithSpace(new ArrayList<>(List.of( - DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW, - DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT, - getPatchSetReviewPrompt() - ))) - )); - if (config.getAIReviewCommitMessages()) { - steps.add(getReviewPromptCommitMessages()); - } - return steps; + private List<String> getReviewSteps() { + List<String> steps = + new ArrayList<>( + List.of( + joinWithSpace( + new ArrayList<>( + List.of( + DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW, + DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT, + getPatchSetReviewPrompt()))))); + if (config.getAIReviewCommitMessages()) { + steps.add(getReviewPromptCommitMessages()); } + return steps; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/model/api/chatgpt/ChatGptCompletionRequest.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/model/api/chatgpt/ChatGptCompletionRequest.java index a65e0a9..de0a81f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/model/api/chatgpt/ChatGptCompletionRequest.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/mode/stateless/model/api/chatgpt/ChatGptCompletionRequest.java
@@ -4,20 +4,20 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatRequestMessage; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatTool; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatToolChoice; +import java.util.List; import lombok.Builder; import lombok.Data; -import java.util.List; - @Data @Builder public class ChatGptCompletionRequest { - private String model; - private boolean stream; - private double temperature; - private int seed; - private List<AIChatRequestMessage> messages; - private AIChatTool[] tools; - @SerializedName("tool_choice") - private AIChatToolChoice toolChoice; + private String model; + private boolean stream; + private double temperature; + private int seed; + private List<AIChatRequestMessage> messages; + private AIChatTool[] tools; + + @SerializedName("tool_choice") + private AIChatToolChoice toolChoice; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/settings/Settings.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/settings/Settings.java index 00dc4c5..e340f47 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/settings/Settings.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/settings/Settings.java
@@ -3,34 +3,36 @@ import java.util.Map; public class Settings { - public static final String GERRIT_PATCH_SET_FILENAME = "/PATCHSET_LEVEL"; - public static final String GERRIT_DEFAULT_MESSAGE_DONE = "Done"; - public static final String GERRIT_AUTOGENERATED_PREFIX = "autogenerated:"; - public static final Map<String , String> COMMIT_MESSAGE_FILTER_OUT_PREFIXES = Map.of( - "PARENT","Parent:", - "AUTHOR","Author:", - "AUTHOR_DATE", "AuthorDate:", - "COMMIT","Commit:", - "COMMIT_DATE", "CommitDate:", - "CHANGE_ID", "Change-Id:" - ); - public static final String GERRIT_COMMIT_MESSAGE_PREFIX = "Subject: "; + public static final String GERRIT_PATCH_SET_FILENAME = "/PATCHSET_LEVEL"; + public static final String GERRIT_DEFAULT_MESSAGE_DONE = "Done"; + public static final String GERRIT_AUTOGENERATED_PREFIX = "autogenerated:"; + public static final Map<String, String> COMMIT_MESSAGE_FILTER_OUT_PREFIXES = + Map.of( + "PARENT", "Parent:", + "AUTHOR", "Author:", + "AUTHOR_DATE", "AuthorDate:", + "COMMIT", "Commit:", + "COMMIT_DATE", "CommitDate:", + "CHANGE_ID", "Change-Id:"); + public static final String GERRIT_COMMIT_MESSAGE_PREFIX = "Subject: "; - public static final String OPEN_AI_CHAT_ROLE_USER = "user"; - public static final String OPEN_AI_CHAT_ROLE_ASSISTANT = "assistant"; + public static final String OPEN_AI_CHAT_ROLE_USER = "user"; + public static final String OPEN_AI_CHAT_ROLE_ASSISTANT = "assistant"; - public enum Modes { - stateless, - stateful - } + public enum Modes { + stateless, + stateful + } - public enum AIType { - CHATGPT, - OLLAMA, - AZUREOPENAI, + public enum AIType { + CHATGPT, + OLLAMA, + AZUREOPENAI, - // used for testing new endpoints and ai services not yet in the supported list, but you can specify the - // endpoint, the authorization header information, and the prompt to quickly prove out a new aiType. - GENERIC; - } + // used for testing new endpoints and ai services not yet in the supported list, but you can + // specify the + // endpoint, the authorization header information, and the prompt to quickly prove out a new + // aiType. + GENERIC; + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/FileUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/FileUtils.java index bad1db3..6ee37d9 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/FileUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/FileUtils.java
@@ -9,31 +9,32 @@ import java.util.Objects; public class FileUtils { - public static InputStreamReader getInputStreamReader(String filename) { - return new InputStreamReader(Objects.requireNonNull( - FileUtils.class.getClassLoader().getResourceAsStream(filename)), StandardCharsets.UTF_8); - } + public static InputStreamReader getInputStreamReader(String filename) { + return new InputStreamReader( + Objects.requireNonNull(FileUtils.class.getClassLoader().getResourceAsStream(filename)), + StandardCharsets.UTF_8); + } - public static Path createTempFileWithContent(String prefix, String suffix, String content) { - Path tempFile; - try { - tempFile = Files.createTempFile(prefix, suffix); - Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new RuntimeException(e); - } - tempFile.toFile().deleteOnExit(); - - return tempFile; + public static Path createTempFileWithContent(String prefix, String suffix, String content) { + Path tempFile; + try { + tempFile = Files.createTempFile(prefix, suffix); + Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); } + tempFile.toFile().deleteOnExit(); - public static boolean matchesExtensionList(String filename, List<String> extensions) { - int extIndex = filename.lastIndexOf('.'); - return extIndex >= 1 && extensions.contains(filename.substring(extIndex)); - } + return tempFile; + } - public static String sanitizeFilename (String filename) { - // Replace any characters that are invalid in filenames (especially slashes) with a "+" - return filename.replaceAll("[^-_a-zA-Z0-9]", "+"); - } + public static boolean matchesExtensionList(String filename, List<String> extensions) { + int extIndex = filename.lastIndexOf('.'); + return extIndex >= 1 && extensions.contains(filename.substring(extIndex)); + } + + public static String sanitizeFilename(String filename) { + // Replace any characters that are invalid in filenames (especially slashes) with a "+" + return filename.replaceAll("[^-_a-zA-Z0-9]", "+"); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/GsonUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/GsonUtils.java index 82d665c..7f1fb40 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/GsonUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/GsonUtils.java
@@ -4,13 +4,11 @@ import com.google.gson.GsonBuilder; public class GsonUtils { - public static Gson getGson() { - return new Gson(); - } + public static Gson getGson() { + return new Gson(); + } - public static Gson getNoEscapedGson() { - return new GsonBuilder() - .disableHtmlEscaping() - .create(); - } + public static Gson getNoEscapedGson() { + return new GsonBuilder().disableHtmlEscaping().create(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/HashUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/HashUtils.java index 4c09f76..8e9d724 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/HashUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/HashUtils.java
@@ -7,33 +7,33 @@ public class HashUtils { - public static String hashData(List<String> dataItems) { - StringBuilder concatenatedData = new StringBuilder(); - for (String item : dataItems) { - concatenatedData.append(item); - } - return sha1(concatenatedData.toString()); + public static String hashData(List<String> dataItems) { + StringBuilder concatenatedData = new StringBuilder(); + for (String item : dataItems) { + concatenatedData.append(item); } + return sha1(concatenatedData.toString()); + } - private static String sha1(String data) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] hashBytes = digest.digest(data.getBytes(StandardCharsets.UTF_8)); - return bytesToHex(hashBytes); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-1 algorithm not found", e); - } + private static String sha1(String data) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] hashBytes = digest.digest(data.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hashBytes); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 algorithm not found", e); } + } - private static String bytesToHex(byte[] hashBytes) { - StringBuilder hexString = new StringBuilder(2 * hashBytes.length); - for (byte b : hashBytes) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); + private static String bytesToHex(byte[] hashBytes) { + StringBuilder hexString = new StringBuilder(2 * hashBytes.length); + for (byte b : hashBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); } + return hexString.toString(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/JsonTextUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/JsonTextUtils.java index d1b35a8..5f29c96 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/JsonTextUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/JsonTextUtils.java
@@ -1,20 +1,20 @@ package com.googlesource.gerrit.plugins.aicodereview.utils; -import lombok.extern.slf4j.Slf4j; - import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; @Slf4j public class JsonTextUtils extends TextUtils { - private static final Pattern JSON_DELIMITED = Pattern.compile("^.*?" + CODE_DELIMITER + "json\\s*(.*)\\s*" + - CODE_DELIMITER + ".*$", Pattern.DOTALL); - private static final Pattern JSON_OBJECT = Pattern.compile("^\\{.*\\}$", Pattern.DOTALL); + private static final Pattern JSON_DELIMITED = + Pattern.compile( + "^.*?" + CODE_DELIMITER + "json\\s*(.*)\\s*" + CODE_DELIMITER + ".*$", Pattern.DOTALL); + private static final Pattern JSON_OBJECT = Pattern.compile("^\\{.*\\}$", Pattern.DOTALL); - public static String unwrapJsonCode(String text) { - return JSON_DELIMITED.matcher(text).replaceAll("$1"); - } + public static String unwrapJsonCode(String text) { + return JSON_DELIMITED.matcher(text).replaceAll("$1"); + } - public static boolean isJsonString(String text) { - return JSON_OBJECT.matcher(text).matches() || JSON_DELIMITED.matcher(text).matches(); - } + public static boolean isJsonString(String text) { + return JSON_OBJECT.matcher(text).matches() || JSON_DELIMITED.matcher(text).matches(); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/StringUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/StringUtils.java index dd7a9c0..2f37a4e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/StringUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/StringUtils.java
@@ -1,28 +1,27 @@ package com.googlesource.gerrit.plugins.aicodereview.utils; -import lombok.extern.slf4j.Slf4j; - import java.util.List; +import lombok.extern.slf4j.Slf4j; @Slf4j public class StringUtils { - public static String backslashEachChar(String body) { - StringBuilder slashedBody = new StringBuilder(); + public static String backslashEachChar(String body) { + StringBuilder slashedBody = new StringBuilder(); - for (char ch : body.toCharArray()) { - slashedBody.append("\\\\").append(ch); - } - return slashedBody.toString(); + for (char ch : body.toCharArray()) { + slashedBody.append("\\\\").append(ch); } + return slashedBody.toString(); + } - public static String concatenate(List<String> components) { - return String.join("", components); - } + public static String concatenate(List<String> components) { + return String.join("", components); + } - public static String capitalizeFirstLetter(String str) { - if (str == null || str.isEmpty()) { - return str; - } - return str.substring(0, 1).toUpperCase() + str.substring(1); + public static String capitalizeFirstLetter(String str) { + if (str == null || str.isEmpty()) { + return str; } + return str.substring(0, 1).toUpperCase() + str.substring(1); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TextUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TextUtils.java index f70b4a4..81704b8 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TextUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TextUtils.java
@@ -1,98 +1,108 @@ package com.googlesource.gerrit.plugins.aicodereview.utils; -import lombok.extern.slf4j.Slf4j; - import java.lang.reflect.Field; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; +import lombok.extern.slf4j.Slf4j; @Slf4j public class TextUtils extends StringUtils { - public static final String INLINE_CODE_DELIMITER = "`"; - public static final String CODE_DELIMITER = "```"; - public static final String CODE_DELIMITER_BEGIN ="\n\n" + CODE_DELIMITER + "\n"; - public static final String CODE_DELIMITER_END ="\n" + CODE_DELIMITER + "\n"; - public static final String SPACE = " "; - public static final String DOT = "."; - public static final String COMMA_SPACE = ", "; - public static final String COLON_SPACE = ": "; - public static final String SEMICOLON_SPACE = "; "; + public static final String INLINE_CODE_DELIMITER = "`"; + public static final String CODE_DELIMITER = "```"; + public static final String CODE_DELIMITER_BEGIN = "\n\n" + CODE_DELIMITER + "\n"; + public static final String CODE_DELIMITER_END = "\n" + CODE_DELIMITER + "\n"; + public static final String SPACE = " "; + public static final String DOT = "."; + public static final String COMMA_SPACE = ", "; + public static final String COLON_SPACE = ": "; + public static final String SEMICOLON_SPACE = "; "; - public static String parseOutOfDelimiters(String body, String splitDelim, Function<String, String> processMessage, - String leftDelimReplacement, String rightDelimReplacement) { - String[] chunks = body.split(splitDelim, -1); - List<String> resultChunks = new ArrayList<>(); - int lastChunk = chunks.length -1; - for (int i = 0; i <= lastChunk; i++) { - String chunk = chunks[i]; - if (i % 2 == 0 || i == lastChunk) { - resultChunks.add(processMessage.apply(chunk)); - } - else { - resultChunks.addAll(Arrays.asList(leftDelimReplacement, chunk, rightDelimReplacement)); - } - } - return concatenate(resultChunks); + public static String parseOutOfDelimiters( + String body, + String splitDelim, + Function<String, String> processMessage, + String leftDelimReplacement, + String rightDelimReplacement) { + String[] chunks = body.split(splitDelim, -1); + List<String> resultChunks = new ArrayList<>(); + int lastChunk = chunks.length - 1; + for (int i = 0; i <= lastChunk; i++) { + String chunk = chunks[i]; + if (i % 2 == 0 || i == lastChunk) { + resultChunks.add(processMessage.apply(chunk)); + } else { + resultChunks.addAll(Arrays.asList(leftDelimReplacement, chunk, rightDelimReplacement)); + } } + return concatenate(resultChunks); + } - public static String parseOutOfDelimiters(String body, String splitDelim, Function<String, String> processMessage) { - return parseOutOfDelimiters(body, splitDelim, processMessage, splitDelim, splitDelim); - } + public static String parseOutOfDelimiters( + String body, String splitDelim, Function<String, String> processMessage) { + return parseOutOfDelimiters(body, splitDelim, processMessage, splitDelim, splitDelim); + } - public static String joinWithNewLine(List<String> components) { - return String.join("\n", components); - } + public static String joinWithNewLine(List<String> components) { + return String.join("\n", components); + } - public static String joinWithDoubleNewLine(List<String> components) { - return String.join("\n\n", components); - } + public static String joinWithDoubleNewLine(List<String> components) { + return String.join("\n\n", components); + } - public static String joinWithSpace(List<String> components) { - return String.join(SPACE, components); - } + public static String joinWithSpace(List<String> components) { + return String.join(SPACE, components); + } - public static String joinWithComma(Set<String> components) { - return String.join(COMMA_SPACE, components); - } + public static String joinWithComma(Set<String> components) { + return String.join(COMMA_SPACE, components); + } - public static String joinWithSemicolon(List<String> components) { - return String.join(SEMICOLON_SPACE, components); - } + public static String joinWithSemicolon(List<String> components) { + return String.join(SEMICOLON_SPACE, components); + } - public static List<String> getNumberedList(List<String> components, String prefix, String postfix) { - return IntStream.range(0, components.size()) - .mapToObj(i -> Optional.ofNullable(prefix).orElse("") + - (i + 1) + - Optional.ofNullable(postfix).orElse(". ") + - components.get(i) - ) - .collect(Collectors.toList()); - } + public static List<String> getNumberedList( + List<String> components, String prefix, String postfix) { + return IntStream.range(0, components.size()) + .mapToObj( + i -> + Optional.ofNullable(prefix).orElse("") + + (i + 1) + + Optional.ofNullable(postfix).orElse(". ") + + components.get(i)) + .collect(Collectors.toList()); + } - public static String getNumberedListString(List<String> components, String prefix, String postfix) { - return joinWithSemicolon(getNumberedList(components, prefix, postfix)); - } + public static String getNumberedListString( + List<String> components, String prefix, String postfix) { + return joinWithSemicolon(getNumberedList(components, prefix, postfix)); + } - public static String prettyStringifyObject(Object object) { - List<String> lines = new ArrayList<>(); - for (Field field : object.getClass().getDeclaredFields()) { - field.setAccessible(true); - try { - lines.add(field.getName() + ": " + field.get(object)); - } catch (IllegalAccessException e) { - log.debug("Error while accessing field {} in {}", field.getName(), object, e); - } - } - return joinWithNewLine(lines); + public static String prettyStringifyObject(Object object) { + List<String> lines = new ArrayList<>(); + for (Field field : object.getClass().getDeclaredFields()) { + field.setAccessible(true); + try { + lines.add(field.getName() + ": " + field.get(object)); + } catch (IllegalAccessException e) { + log.debug("Error while accessing field {} in {}", field.getName(), object, e); + } } + return joinWithNewLine(lines); + } - public static String prettyStringifyMap(Map<String, String> map) { - return joinWithNewLine( - map.entrySet().stream() - .map(entry -> entry.getKey() + ": " + entry.getValue()) - .collect(Collectors.toList()) - ); - } + public static String prettyStringifyMap(Map<String, String> map) { + return joinWithNewLine( + map.entrySet().stream() + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.toList())); + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/ThreadUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/ThreadUtils.java index 6ee8aac..6d938f6 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/ThreadUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/ThreadUtils.java
@@ -1,12 +1,12 @@ package com.googlesource.gerrit.plugins.aicodereview.utils; public class ThreadUtils { - public static void threadSleep(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Thread was interrupted", e); - } + public static void threadSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Thread was interrupted", e); } + } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TimeUtils.java b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TimeUtils.java index 8d7b0c5..5b86eea 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TimeUtils.java +++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/utils/TimeUtils.java
@@ -5,9 +5,9 @@ import java.time.format.DateTimeFormatter; public class TimeUtils { - public static long getTimeStamp(String updatedString) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"); - LocalDateTime updatedDateTime = LocalDateTime.parse(updatedString, formatter); - return updatedDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); - } + public static long getTimeStamp(String updatedString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"); + LocalDateTime updatedDateTime = LocalDateTime.parse(updatedString, formatter); + return updatedDateTime.toInstant(ZoneOffset.UTC).getEpochSecond(); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatefulTest.java index b31b9f7..7a32c2b 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatefulTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatefulTest.java
@@ -1,5 +1,14 @@ package com.googlesource.gerrit.plugins.aicodereview; +import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatPromptFactory.getAIChatPromptStateful; +import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptRun.COMPLETED_STATUS; +import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptVectorStore.KEY_VECTOR_STORE_ID; +import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.github.tomakehurst.wiremock.client.WireMock; import com.google.common.net.HttpHeaders; import com.google.gerrit.extensions.api.changes.FileApi; @@ -10,11 +19,14 @@ import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt.ChatGptPromptStateful; +import com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.SupportedEvents; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai.AIChatResponseContent; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.UriResourceLocatorStateful; import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt.ChatGptListResponse; import com.googlesource.gerrit.plugins.aicodereview.settings.Settings.Modes; import com.googlesource.gerrit.plugins.aicodereview.utils.ThreadUtils; +import java.io.ByteArrayInputStream; +import java.net.URI; import lombok.extern.slf4j.Slf4j; import org.apache.http.entity.ContentType; import org.junit.Assert; @@ -26,274 +38,351 @@ import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; -import java.io.ByteArrayInputStream; -import java.net.URI; - -import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.SupportedEvents; -import static com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt.AIChatPromptFactory.getAIChatPromptStateful; -import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptRun.COMPLETED_STATUS; -import static com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.chatgpt.ChatGptVectorStore.KEY_VECTOR_STORE_ID; -import static com.googlesource.gerrit.plugins.aicodereview.settings.Settings.GERRIT_PATCH_SET_FILENAME; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; -import static java.net.HttpURLConnection.HTTP_OK; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @Slf4j @RunWith(MockitoJUnitRunner.class) public class AIChatReviewStatefulTest extends AIChatReviewTestBase { - private static final String AI_CHAT_FILE_ID = "file-TEST_FILE_ID"; - private static final String AI_CHAT_VECTOR_ID = "file-TEST_VECTOR_ID"; - private static final String AI_CHAT_ASSISTANT_ID = "asst_TEST_ASSISTANT_ID"; - private static final String AI_CHAT_THREAD_ID = "thread_TEST_THREAD_ID"; - private static final String AI_CHAT_MESSAGE_ID = "msg_TEST_MESSAGE_ID"; - private static final String AI_CHAT_RUN_ID = "run_TEST_RUN_ID"; + private static final String AI_CHAT_FILE_ID = "file-TEST_FILE_ID"; + private static final String AI_CHAT_VECTOR_ID = "file-TEST_VECTOR_ID"; + private static final String AI_CHAT_ASSISTANT_ID = "asst_TEST_ASSISTANT_ID"; + private static final String AI_CHAT_THREAD_ID = "thread_TEST_THREAD_ID"; + private static final String AI_CHAT_MESSAGE_ID = "msg_TEST_MESSAGE_ID"; + private static final String AI_CHAT_RUN_ID = "run_TEST_RUN_ID"; - private String formattedPatchContent; - private ChatGptPromptStateful chatGptPromptStateful; - private String requestContent; - private PluginDataHandler projectHandler; + private String formattedPatchContent; + private ChatGptPromptStateful chatGptPromptStateful; + private String requestContent; + private PluginDataHandler projectHandler; - public AIChatReviewStatefulTest() { - MockitoAnnotations.openMocks(this); - } + public AIChatReviewStatefulTest() { + MockitoAnnotations.openMocks(this); + } - protected void initGlobalAndProjectConfig() { - super.initGlobalAndProjectConfig(); + protected void initGlobalAndProjectConfig() { + super.initGlobalAndProjectConfig(); - // Mock the Global Config values that differ from the ones provided by Default - when(globalConfig.getString(Mockito.eq("aiMode"), Mockito.anyString())) - .thenReturn(Modes.stateful.name()); + // Mock the Global Config values that differ from the ones provided by Default + when(globalConfig.getString(Mockito.eq("aiMode"), Mockito.anyString())) + .thenReturn(Modes.stateful.name()); - setupPluginData(); - PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); - projectHandler = provider.getProjectScope(); - // Mock the pluginDataHandlerProvider to return the mocked project pluginDataHandler - when(pluginDataHandlerProvider.getProjectScope()).thenReturn(projectHandler); - // Mock the pluginDataHandlerProvider to return the mocked assistant pluginDataHandler - when(pluginDataHandlerProvider.getAssistantsWorkspace()).thenReturn(projectHandler); - } + setupPluginData(); + PluginDataHandlerProvider provider = + new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); + projectHandler = provider.getProjectScope(); + // Mock the pluginDataHandlerProvider to return the mocked project pluginDataHandler + when(pluginDataHandlerProvider.getProjectScope()).thenReturn(projectHandler); + // Mock the pluginDataHandlerProvider to return the mocked assistant pluginDataHandler + when(pluginDataHandlerProvider.getAssistantsWorkspace()).thenReturn(projectHandler); + } - protected void initTest() { - super.initTest(); + protected void initTest() { + super.initTest(); - // Load the prompts - chatGptPromptStateful = getAIChatPromptStateful(config, changeSetData, getGerritChange()); - } + // Load the prompts + chatGptPromptStateful = getAIChatPromptStateful(config, changeSetData, getGerritChange()); + } - protected void setupMockRequests() throws RestApiException { - super.setupMockRequests(); + protected void setupMockRequests() throws RestApiException { + super.setupMockRequests(); - // Mock the behavior of the Git Repository Manager - String repoJson = readTestFile("__files/stateful/gitProjectFiles.json"); - when(gitRepoFiles.getGitRepoFiles(any(), any())).thenReturn(repoJson); + // Mock the behavior of the Git Repository Manager + String repoJson = readTestFile("__files/stateful/gitProjectFiles.json"); + when(gitRepoFiles.getGitRepoFiles(any(), any())).thenReturn(repoJson); - // Mock the behavior of the ChatGPT create-file request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.filesCreateUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_FILE_ID + "}"))); + // Mock the behavior of the ChatGPT create-file request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create(config.getAIDomain() + UriResourceLocatorStateful.filesCreateUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_FILE_ID + "}"))); - // Mock the behavior of the ChatGPT create-vector-store request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.vectorStoreCreateUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_VECTOR_ID + "}"))); + // Mock the behavior of the ChatGPT create-vector-store request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.vectorStoreCreateUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_VECTOR_ID + "}"))); - // Mock the behavior of the ChatGPT create-assistant request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.assistantCreateUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_ASSISTANT_ID + "}"))); + // Mock the behavior of the ChatGPT create-assistant request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + UriResourceLocatorStateful.assistantCreateUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_ASSISTANT_ID + "}"))); - // Mock the behavior of the ChatGPT create-thread request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.threadsUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_THREAD_ID + "}"))); + // Mock the behavior of the ChatGPT create-thread request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create(config.getAIDomain() + UriResourceLocatorStateful.threadsUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_THREAD_ID + "}"))); - // Mock the behavior of the ChatGPT add-message-to-thread request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.threadMessagesUri(AI_CHAT_THREAD_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_MESSAGE_ID + "}"))); + // Mock the behavior of the ChatGPT add-message-to-thread request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.threadMessagesUri(AI_CHAT_THREAD_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_MESSAGE_ID + "}"))); - // Mock the behavior of the ChatGPT create-run request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.runsUri(AI_CHAT_THREAD_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"id\": " + AI_CHAT_RUN_ID + "}"))); + // Mock the behavior of the ChatGPT create-run request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.runsUri(AI_CHAT_THREAD_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"id\": " + AI_CHAT_RUN_ID + "}"))); - // Mock the behavior of the ChatGPT retrieve-run request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.runRetrieveUri(AI_CHAT_THREAD_ID, AI_CHAT_RUN_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBody("{\"status\": " + COMPLETED_STATUS + "}"))); + // Mock the behavior of the ChatGPT retrieve-run request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.runRetrieveUri( + AI_CHAT_THREAD_ID, AI_CHAT_RUN_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBody("{\"status\": " + COMPLETED_STATUS + "}"))); - mockRetrieveRunSteps("chatGptRunStepsResponse.json"); + mockRetrieveRunSteps("chatGptRunStepsResponse.json"); - // Mock the behavior of the formatted patch request - formattedPatchContent = readTestFile("__files/stateful/gerritFormattedPatch.txt"); - ByteArrayInputStream inputStream = new ByteArrayInputStream(formattedPatchContent.getBytes()); - BinaryResult binaryResult = BinaryResult.create(inputStream) - .setContentType("text/plain") - .setContentLength(formattedPatchContent.length()); - when(revisionApiMock.patch()).thenReturn(binaryResult); + // Mock the behavior of the formatted patch request + formattedPatchContent = readTestFile("__files/stateful/gerritFormattedPatch.txt"); + ByteArrayInputStream inputStream = new ByteArrayInputStream(formattedPatchContent.getBytes()); + BinaryResult binaryResult = + BinaryResult.create(inputStream) + .setContentType("text/plain") + .setContentLength(formattedPatchContent.length()); + when(revisionApiMock.patch()).thenReturn(binaryResult); - FileApi testFileMock = mock(FileApi.class); - when(revisionApiMock.file("test_file_1.py")).thenReturn(testFileMock); - DiffInfo testFileDiff = readTestFileToClass("__files/stateful/gerritPatchSetDiffTestFile.json", DiffInfo.class); - when(testFileMock.diff(0)).thenReturn(testFileDiff); - } + FileApi testFileMock = mock(FileApi.class); + when(revisionApiMock.file("test_file_1.py")).thenReturn(testFileMock); + DiffInfo testFileDiff = + readTestFileToClass("__files/stateful/gerritPatchSetDiffTestFile.json", DiffInfo.class); + when(testFileMock.diff(0)).thenReturn(testFileDiff); + } - protected void initComparisonContent() { - super.initComparisonContent(); + protected void initComparisonContent() { + super.initComparisonContent(); - promptTagComments = readTestFile("__files/stateful/aiChatPromptTagRequests.json"); - } + promptTagComments = readTestFile("__files/stateful/aiChatPromptTagRequests.json"); + } - protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { - ArgumentCaptor<ReviewInput> reviewInputCaptor = super.testRequestSent(); - requestContent = gptRequestBody.getAsJsonObject().get("content").getAsString(); - return reviewInputCaptor; - } + protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { + ArgumentCaptor<ReviewInput> reviewInputCaptor = super.testRequestSent(); + requestContent = gptRequestBody.getAsJsonObject().get("content").getAsString(); + return reviewInputCaptor; + } - private String getReviewMessage(String responseFile, int tollCallId) { - ChatGptListResponse responseContent = getGson().fromJson(readTestFile(responseFile), ChatGptListResponse.class); - String reviewJsonResponse = responseContent.getData().get(0).getStepDetails().getToolCalls().get(tollCallId) - .getFunction().getArguments(); - return getGson().fromJson(reviewJsonResponse, AIChatResponseContent.class).getReplies().get(0).getReply(); - } + private String getReviewMessage(String responseFile, int tollCallId) { + ChatGptListResponse responseContent = + getGson().fromJson(readTestFile(responseFile), ChatGptListResponse.class); + String reviewJsonResponse = + responseContent + .getData() + .get(0) + .getStepDetails() + .getToolCalls() + .get(tollCallId) + .getFunction() + .getArguments(); + return getGson() + .fromJson(reviewJsonResponse, AIChatResponseContent.class) + .getReplies() + .get(0) + .getReply(); + } - private String getCapturedMessage(ArgumentCaptor<ReviewInput> captor, String filename) { - return captor.getAllValues().get(0).comments.get(filename).get(0).message; - } + private String getCapturedMessage(ArgumentCaptor<ReviewInput> captor, String filename) { + return captor.getAllValues().get(0).comments.get(filename).get(0).message; + } - private void mockRetrieveRunSteps(String bodyFile) { - // Mock the behavior of the ChatGPT retrieve-run-steps request - WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.runStepsUri(AI_CHAT_THREAD_ID, AI_CHAT_RUN_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile(bodyFile))); - } + private void mockRetrieveRunSteps(String bodyFile) { + // Mock the behavior of the ChatGPT retrieve-run-steps request + WireMock.stubFor( + WireMock.get( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.runStepsUri( + AI_CHAT_THREAD_ID, AI_CHAT_RUN_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile(bodyFile))); + } - @Test - public void patchSetCreatedOrUpdated() throws Exception { - String reviewMessageCode = getReviewMessage( "__files/chatGptRunStepsResponse.json", 0); - String reviewMessageCommitMessage = getReviewMessage( "__files/chatGptRunStepsResponse.json", 1); + @Test + public void patchSetCreatedOrUpdated() throws Exception { + String reviewMessageCode = getReviewMessage("__files/chatGptRunStepsResponse.json", 0); + String reviewMessageCommitMessage = getReviewMessage("__files/chatGptRunStepsResponse.json", 1); - String reviewPrompt = chatGptPromptStateful.getDefaultGptThreadReviewMessage(formattedPatchContent); + String reviewPrompt = + chatGptPromptStateful.getDefaultGptThreadReviewMessage(formattedPatchContent); - handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); + handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - Assert.assertEquals(reviewPrompt, requestContent); - Assert.assertEquals(reviewMessageCode, getCapturedMessage(captor, "test_file_1.py")); - Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); - } + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + Assert.assertEquals(reviewPrompt, requestContent); + Assert.assertEquals(reviewMessageCode, getCapturedMessage(captor, "test_file_1.py")); + Assert.assertEquals( + reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); + } - @Test - public void initialEmptyResponse() throws Exception { - // To effectively test how an initial empty response from ChatGPT is managed, the following approach is adopted: - // 1. the ChatGPT run-steps request is initially mocked to return an empty data field, and - // 2. the sleep function is mocked to replace the empty response with a valid one, instead of pausing execution - mockRetrieveRunSteps("chatGptRunStepsEmptyResponse.json"); + @Test + public void initialEmptyResponse() throws Exception { + // To effectively test how an initial empty response from ChatGPT is managed, the following + // approach is adopted: + // 1. the ChatGPT run-steps request is initially mocked to return an empty data field, and + // 2. the sleep function is mocked to replace the empty response with a valid one, instead of + // pausing execution + mockRetrieveRunSteps("chatGptRunStepsEmptyResponse.json"); - try (MockedStatic<ThreadUtils> mocked = Mockito.mockStatic(ThreadUtils.class)) { - mocked.when(() -> ThreadUtils.threadSleep(Mockito.anyLong())).thenAnswer(invocation -> { + try (MockedStatic<ThreadUtils> mocked = Mockito.mockStatic(ThreadUtils.class)) { + mocked + .when(() -> ThreadUtils.threadSleep(Mockito.anyLong())) + .thenAnswer( + invocation -> { mockRetrieveRunSteps("chatGptRunStepsResponse.json"); return null; - }); + }); - String reviewMessageCode = getReviewMessage("__files/chatGptRunStepsResponse.json", 0); - String reviewMessageCommitMessage = getReviewMessage("__files/chatGptRunStepsResponse.json", 1); + String reviewMessageCode = getReviewMessage("__files/chatGptRunStepsResponse.json", 0); + String reviewMessageCommitMessage = + getReviewMessage("__files/chatGptRunStepsResponse.json", 1); - String reviewPrompt = chatGptPromptStateful.getDefaultGptThreadReviewMessage(formattedPatchContent); + String reviewPrompt = + chatGptPromptStateful.getDefaultGptThreadReviewMessage(formattedPatchContent); - handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); + handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - Assert.assertEquals(reviewPrompt, requestContent); - Assert.assertEquals(reviewMessageCode, getCapturedMessage(captor, "test_file_1.py")); - Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); - } + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + Assert.assertEquals(reviewPrompt, requestContent); + Assert.assertEquals(reviewMessageCode, getCapturedMessage(captor, "test_file_1.py")); + Assert.assertEquals( + reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); } + } - @Test - public void gptMentionedInComment() throws RestApiException { - String reviewMessageCommitMessage = getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); + @Test + public void gptMentionedInComment() throws RestApiException { + String reviewMessageCommitMessage = + getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); - chatGptPromptStateful.setCommentEvent(true); - mockRetrieveRunSteps("chatGptResponseRequestStateful.json"); + chatGptPromptStateful.setCommentEvent(true); + mockRetrieveRunSteps("chatGptResponseRequestStateful.json"); - handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); + handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - Assert.assertEquals(promptTagComments, requestContent); - Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); - } + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + Assert.assertEquals(promptTagComments, requestContent); + Assert.assertEquals( + reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); + } - @Test - public void gptMentionedInCommentMessageResponseText() throws RestApiException { - String reviewMessageCommitMessage = getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); + @Test + public void gptMentionedInCommentMessageResponseText() throws RestApiException { + String reviewMessageCommitMessage = + getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); - chatGptPromptStateful.setCommentEvent(true); - mockRetrieveRunSteps("chatGptResponseRequestMessageStateful.json"); - WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.threadMessageRetrieveUri(AI_CHAT_THREAD_ID, AI_CHAT_MESSAGE_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile("chatGptResponseThreadMessageText.json"))); + chatGptPromptStateful.setCommentEvent(true); + mockRetrieveRunSteps("chatGptResponseRequestMessageStateful.json"); + WireMock.stubFor( + WireMock.get( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.threadMessageRetrieveUri( + AI_CHAT_THREAD_ID, AI_CHAT_MESSAGE_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile("chatGptResponseThreadMessageText.json"))); - handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); + handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - Assert.assertEquals(promptTagComments, requestContent); - Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); - } + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + Assert.assertEquals(promptTagComments, requestContent); + Assert.assertEquals( + reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); + } - @Test - public void gptMentionedInCommentMessageResponseJson() throws RestApiException { - String reviewMessageCommitMessage = getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); + @Test + public void gptMentionedInCommentMessageResponseJson() throws RestApiException { + String reviewMessageCommitMessage = + getReviewMessage("__files/chatGptResponseRequestStateful.json", 0); - chatGptPromptStateful.setCommentEvent(true); - mockRetrieveRunSteps("chatGptResponseRequestMessageStateful.json"); - WireMock.stubFor(WireMock.get(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateful.threadMessageRetrieveUri(AI_CHAT_THREAD_ID, AI_CHAT_MESSAGE_ID)).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile("chatGptResponseThreadMessageJson.json"))); + chatGptPromptStateful.setCommentEvent(true); + mockRetrieveRunSteps("chatGptResponseRequestMessageStateful.json"); + WireMock.stubFor( + WireMock.get( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + + UriResourceLocatorStateful.threadMessageRetrieveUri( + AI_CHAT_THREAD_ID, AI_CHAT_MESSAGE_ID)) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile("chatGptResponseThreadMessageJson.json"))); - handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); + handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - Assert.assertEquals(promptTagComments, requestContent); - Assert.assertEquals(reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); - } + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + Assert.assertEquals(promptTagComments, requestContent); + Assert.assertEquals( + reviewMessageCommitMessage, getCapturedMessage(captor, GERRIT_PATCH_SET_FILENAME)); + } - @Test - public void gerritMergedCommits() { - projectHandler.removeValue(KEY_VECTOR_STORE_ID); - handleEventBasedOnType(SupportedEvents.CHANGE_MERGED); + @Test + public void gerritMergedCommits() { + projectHandler.removeValue(KEY_VECTOR_STORE_ID); + handleEventBasedOnType(SupportedEvents.CHANGE_MERGED); - Assert.assertEquals(AI_CHAT_VECTOR_ID, projectHandler.getValue(KEY_VECTOR_STORE_ID)); - } + Assert.assertEquals(AI_CHAT_VECTOR_ID, projectHandler.getValue(KEY_VECTOR_STORE_ID)); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatelessTest.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatelessTest.java index d25c9c9..1799b34 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatelessTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewStatelessTest.java
@@ -1,9 +1,15 @@ package com.googlesource.gerrit.plugins.aicodereview; +import static com.googlesource.gerrit.plugins.aicodereview.config.Configuration.KEY_AI_CHAT_ENDPOINT; +import static com.googlesource.gerrit.plugins.aicodereview.config.Configuration.KEY_AI_TYPE; +import static com.googlesource.gerrit.plugins.aicodereview.config.Configuration.KEY_STREAM_OUTPUT; +import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.github.tomakehurst.wiremock.client.WireMock; import com.google.common.net.HttpHeaders; -import com.google.gson.JsonArray; -import com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask; import com.google.gerrit.extensions.api.changes.FileApi; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.common.DiffInfo; @@ -11,11 +17,16 @@ import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.json.OutputFormat; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask; +import com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.SupportedEvents; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.UriResourceLocatorStateless; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.prompt.AIChatPromptStateless; import com.googlesource.gerrit.plugins.aicodereview.settings.Settings; +import java.net.URI; +import java.util.Arrays; +import java.util.Map; import lombok.extern.slf4j.Slf4j; - import org.apache.commons.lang3.reflect.TypeLiteral; import org.apache.http.entity.ContentType; import org.junit.Assert; @@ -25,235 +36,250 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import java.net.URI; -import java.util.Arrays; -import java.util.Map; - -import static com.googlesource.gerrit.plugins.aicodereview.config.Configuration.*; -import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.SupportedEvents; -import static com.googlesource.gerrit.plugins.aicodereview.utils.TextUtils.joinWithNewLine; -import static java.net.HttpURLConnection.HTTP_OK; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - @Slf4j @RunWith(MockitoJUnitRunner.class) public class AIChatReviewStatelessTest extends AIChatReviewTestBase { - private ReviewInput expectedResponseStreamed; - private String expectedSystemPromptReview; - private String promptTagReview; - private String diffContent; - private ReviewInput gerritPatchSetReview; - private JsonArray prompts; + private ReviewInput expectedResponseStreamed; + private String expectedSystemPromptReview; + private String promptTagReview; + private String diffContent; + private ReviewInput gerritPatchSetReview; + private JsonArray prompts; - private AIChatPromptStateless AIChatPromptStateless; + private AIChatPromptStateless AIChatPromptStateless; - protected void initConfig() { - super.initGlobalAndProjectConfig(); + protected void initConfig() { + super.initGlobalAndProjectConfig(); - when(globalConfig.getBoolean(Mockito.eq(KEY_STREAM_OUTPUT), Mockito.anyBoolean())) - .thenReturn(GPT_STREAM_OUTPUT); - when(globalConfig.getBoolean(Mockito.eq("aiReviewCommitMessages"), Mockito.anyBoolean())) - .thenReturn(true); + when(globalConfig.getBoolean(Mockito.eq(KEY_STREAM_OUTPUT), Mockito.anyBoolean())) + .thenReturn(GPT_STREAM_OUTPUT); + when(globalConfig.getBoolean(Mockito.eq("aiReviewCommitMessages"), Mockito.anyBoolean())) + .thenReturn(true); - super.initConfig(); + super.initConfig(); - // Load the prompts - AIChatPromptStateless = new AIChatPromptStateless(config); - } + // Load the prompts + AIChatPromptStateless = new AIChatPromptStateless(config); + } - protected void setupMockRequests() throws RestApiException { - super.setupMockRequests(); + protected void setupMockRequests() throws RestApiException { + super.setupMockRequests(); - // Mock the behavior of the gerritPatchSetFiles request - Map<String, FileInfo> files = readTestFileToType( - "__files/stateless/gerritPatchSetFiles.json", - new TypeLiteral<Map<String, FileInfo>>() {}.getType() - ); - when(revisionApiMock.files(0)).thenReturn(files); + // Mock the behavior of the gerritPatchSetFiles request + Map<String, FileInfo> files = + readTestFileToType( + "__files/stateless/gerritPatchSetFiles.json", + new TypeLiteral<Map<String, FileInfo>>() {}.getType()); + when(revisionApiMock.files(0)).thenReturn(files); - // Mock the behavior of the gerritPatchSet diff requests - FileApi commitMsgFileMock = mock(FileApi.class); - when(revisionApiMock.file("/COMMIT_MSG")).thenReturn(commitMsgFileMock); - DiffInfo commitMsgFileDiff = readTestFileToClass("__files/stateless/gerritPatchSetDiffCommitMsg.json", DiffInfo.class); - when(commitMsgFileMock.diff(0)).thenReturn(commitMsgFileDiff); - FileApi testFileMock = mock(FileApi.class); - when(revisionApiMock.file("test_file.py")).thenReturn(testFileMock); - DiffInfo testFileDiff = readTestFileToClass("__files/stateless/gerritPatchSetDiffTestFile.json", DiffInfo.class); - when(testFileMock.diff(0)).thenReturn(testFileDiff); + // Mock the behavior of the gerritPatchSet diff requests + FileApi commitMsgFileMock = mock(FileApi.class); + when(revisionApiMock.file("/COMMIT_MSG")).thenReturn(commitMsgFileMock); + DiffInfo commitMsgFileDiff = + readTestFileToClass("__files/stateless/gerritPatchSetDiffCommitMsg.json", DiffInfo.class); + when(commitMsgFileMock.diff(0)).thenReturn(commitMsgFileDiff); + FileApi testFileMock = mock(FileApi.class); + when(revisionApiMock.file("test_file.py")).thenReturn(testFileMock); + DiffInfo testFileDiff = + readTestFileToClass("__files/stateless/gerritPatchSetDiffTestFile.json", DiffInfo.class); + when(testFileMock.diff(0)).thenReturn(testFileDiff); - // Mock the behavior of the askGpt request - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateless.chatCompletionsUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile("aiChatResponseStreamed.txt"))); - } + // Mock the behavior of the askGpt request + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + UriResourceLocatorStateless.chatCompletionsUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile("aiChatResponseStreamed.txt"))); + } - protected void initComparisonContent() { - super.initComparisonContent(); + protected void initComparisonContent() { + super.initComparisonContent(); - diffContent = readTestFile("reducePatchSet/patchSetDiffOutput.json"); - gerritPatchSetReview = readTestFileToClass("__files/stateless/gerritPatchSetReview.json", ReviewInput.class); - expectedResponseStreamed = readTestFileToClass("__files/stateless/aiChatExpectedResponseStreamed.json", ReviewInput.class); - promptTagReview = readTestFile("__files/stateless/aiChatPromptTagReview.json"); - promptTagComments = readTestFile("__files/stateless/aiChatPromptTagRequests.json"); - expectedSystemPromptReview = AIChatPromptStateless.getDefaultGptReviewSystemPrompt(); - } + diffContent = readTestFile("reducePatchSet/patchSetDiffOutput.json"); + gerritPatchSetReview = + readTestFileToClass("__files/stateless/gerritPatchSetReview.json", ReviewInput.class); + expectedResponseStreamed = + readTestFileToClass( + "__files/stateless/aiChatExpectedResponseStreamed.json", ReviewInput.class); + promptTagReview = readTestFile("__files/stateless/aiChatPromptTagReview.json"); + promptTagComments = readTestFile("__files/stateless/aiChatPromptTagRequests.json"); + expectedSystemPromptReview = AIChatPromptStateless.getDefaultGptReviewSystemPrompt(); + } - protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { - ArgumentCaptor<ReviewInput> reviewInputCaptor = super.testRequestSent(); - prompts = gptRequestBody.get("messages").getAsJsonArray(); - return reviewInputCaptor; - } + protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { + ArgumentCaptor<ReviewInput> reviewInputCaptor = super.testRequestSent(); + prompts = gptRequestBody.get("messages").getAsJsonArray(); + return reviewInputCaptor; + } - private String getReviewUserPrompt() { - return joinWithNewLine(Arrays.asList( - AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT, - AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW + " " + - AIChatPromptStateless.DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT + " " + - AIChatPromptStateless.getPatchSetReviewPrompt(), - AIChatPromptStateless.getReviewPromptCommitMessages(), - AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF, - diffContent, - AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY, - promptTagReview - )); - } + private String getReviewUserPrompt() { + return joinWithNewLine( + Arrays.asList( + AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT, + AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_REVIEW + + " " + + AIChatPromptStateless.DEFAULT_AI_CHAT_PROMPT_FORCE_JSON_FORMAT + + " " + + AIChatPromptStateless.getPatchSetReviewPrompt(), + AIChatPromptStateless.getReviewPromptCommitMessages(), + AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_DIFF, + diffContent, + AIChatPromptStateless.DEFAULT_AI_CHAT_REVIEW_PROMPT_MESSAGE_HISTORY, + promptTagReview)); + } - @Test - public void patchSetCreatedOrUpdatedStreamed() throws Exception { - String reviewUserPrompt = getReviewUserPrompt(); - AIChatPromptStateless.setCommentEvent(false); + @Test + public void patchSetCreatedOrUpdatedStreamed() throws Exception { + String reviewUserPrompt = getReviewUserPrompt(); + AIChatPromptStateless.setCommentEvent(false); - handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); + handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - String systemPrompt = prompts.get(0).getAsJsonObject().get("content").getAsString(); - Assert.assertEquals(expectedSystemPromptReview, systemPrompt); - String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); - Assert.assertEquals(reviewUserPrompt, userPrompt); + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + String systemPrompt = prompts.get(0).getAsJsonObject().get("content").getAsString(); + Assert.assertEquals(expectedSystemPromptReview, systemPrompt); + String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); + Assert.assertEquals(reviewUserPrompt, userPrompt); - Gson gson = OutputFormat.JSON_COMPACT.newGson(); - Assert.assertEquals(gson.toJson(expectedResponseStreamed), gson.toJson(captor.getAllValues().get(0))); - } + Gson gson = OutputFormat.JSON_COMPACT.newGson(); + Assert.assertEquals( + gson.toJson(expectedResponseStreamed), gson.toJson(captor.getAllValues().get(0))); + } - @Test - public void patchSetCreatedOrUpdatedUnstreamed() throws Exception { - when(globalConfig.getBoolean(Mockito.eq("aiStreamOutput"), Mockito.anyBoolean())) - .thenReturn(false); - when(globalConfig.getBoolean(Mockito.eq("enabledVoting"), Mockito.anyBoolean())) - .thenReturn(true); + @Test + public void patchSetCreatedOrUpdatedUnstreamed() throws Exception { + when(globalConfig.getBoolean(Mockito.eq("aiStreamOutput"), Mockito.anyBoolean())) + .thenReturn(false); + when(globalConfig.getBoolean(Mockito.eq("enabledVoting"), Mockito.anyBoolean())) + .thenReturn(true); - String reviewUserPrompt = getReviewUserPrompt(); - AIChatPromptStateless.setCommentEvent(false); - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateless.chatCompletionsUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile("aiChatResponseReview.json"))); + String reviewUserPrompt = getReviewUserPrompt(); + AIChatPromptStateless.setCommentEvent(false); + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + UriResourceLocatorStateless.chatCompletionsUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile("aiChatResponseReview.json"))); - handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); + handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED); - ArgumentCaptor<ReviewInput> captor = testRequestSent(); - String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); - Assert.assertEquals(reviewUserPrompt, userPrompt); + ArgumentCaptor<ReviewInput> captor = testRequestSent(); + String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); + Assert.assertEquals(reviewUserPrompt, userPrompt); - Gson gson = OutputFormat.JSON_COMPACT.newGson(); - Assert.assertEquals(gson.toJson(gerritPatchSetReview), gson.toJson(captor.getAllValues().get(0))); - } + Gson gson = OutputFormat.JSON_COMPACT.newGson(); + Assert.assertEquals( + gson.toJson(gerritPatchSetReview), gson.toJson(captor.getAllValues().get(0))); + } - @Test - public void patchSetDisableUserGroup() { - when(globalConfig.getString(Mockito.eq("disabledGroups"), Mockito.anyString())) - .thenReturn(GERRIT_USER_GROUP); + @Test + public void patchSetDisableUserGroup() { + when(globalConfig.getString(Mockito.eq("disabledGroups"), Mockito.anyString())) + .thenReturn(GERRIT_USER_GROUP); - Assert.assertEquals(EventHandlerTask.Result.NOT_SUPPORTED, handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED)); - } + Assert.assertEquals( + EventHandlerTask.Result.NOT_SUPPORTED, + handleEventBasedOnType(SupportedEvents.PATCH_SET_CREATED)); + } - @Test - public void gptMentionedInComment() throws RestApiException { - when(config.getGerritUserName()).thenReturn(GERRIT_GPT_USERNAME); - AIChatPromptStateless.setCommentEvent(true); - WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getAIDomain() - + UriResourceLocatorStateless.chatCompletionsUri()).getPath())) - .willReturn(WireMock.aResponse() - .withStatus(HTTP_OK) - .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) - .withBodyFile("aiChatResponseRequestStateless.json"))); + @Test + public void gptMentionedInComment() throws RestApiException { + when(config.getGerritUserName()).thenReturn(GERRIT_GPT_USERNAME); + AIChatPromptStateless.setCommentEvent(true); + WireMock.stubFor( + WireMock.post( + WireMock.urlEqualTo( + URI.create( + config.getAIDomain() + UriResourceLocatorStateless.chatCompletionsUri()) + .getPath())) + .willReturn( + WireMock.aResponse() + .withStatus(HTTP_OK) + .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) + .withBodyFile("aiChatResponseRequestStateless.json"))); - handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); - int commentPropertiesSize = gerritClient.getClientData(getGerritChange()).getCommentProperties().size(); + handleEventBasedOnType(SupportedEvents.COMMENT_ADDED); + int commentPropertiesSize = + gerritClient.getClientData(getGerritChange()).getCommentProperties().size(); - String commentUserPrompt = joinWithNewLine(Arrays.asList( + String commentUserPrompt = + joinWithNewLine( + Arrays.asList( AIChatPromptStateless.DEFAULT_AI_CHAT_REQUEST_PROMPT_DIFF, diffContent, AIChatPromptStateless.DEFAULT_AI_CHAT_REQUEST_PROMPT_REQUESTS, readTestFile("__files/stateless/aiChatExpectedRequestMessage.json"), - AIChatPromptStateless.getCommentRequestPrompt(commentPropertiesSize) - )); - testRequestSent(); - String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); - Assert.assertEquals(commentUserPrompt, userPrompt); - } + AIChatPromptStateless.getCommentRequestPrompt(commentPropertiesSize))); + testRequestSent(); + String userPrompt = prompts.get(1).getAsJsonObject().get("content").getAsString(); + Assert.assertEquals(commentUserPrompt, userPrompt); + } - @Test - public void testAITypeValidOptions(){ - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("CHATGPT"); + @Test + public void testAITypeValidOptions() { + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("CHATGPT"); - // check default for aiType is chatGPT. - Assert.assertEquals(config.getAIType(), Settings.AIType.CHATGPT); + // check default for aiType is chatGPT. + Assert.assertEquals(config.getAIType(), Settings.AIType.CHATGPT); - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("OLLAMA"); + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("OLLAMA"); - Assert.assertEquals(config.getAIType(), Settings.AIType.OLLAMA); - } + Assert.assertEquals(config.getAIType(), Settings.AIType.OLLAMA); + } - @Test - public void testAITypeControlsEndpoint(){ - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("CHATGPT"); + @Test + public void testAITypeControlsEndpoint() { + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("CHATGPT"); - // check default for aiType is chatGPT. - Assert.assertEquals(config.getChatEndpoint(), ""); - Assert.assertEquals(UriResourceLocatorStateless.chatCompletionsUri(), - UriResourceLocatorStateless.getChatResourceUri(config)); + // check default for aiType is chatGPT. + Assert.assertEquals(config.getChatEndpoint(), ""); + Assert.assertEquals( + UriResourceLocatorStateless.chatCompletionsUri(), + UriResourceLocatorStateless.getChatResourceUri(config)); - // swap it to ollama, check we still get the chatCompletionsUri, as its the openai - // compat endpoint we use. - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("OLLAMA"); - Assert.assertEquals(UriResourceLocatorStateless.chatCompletionsUri(), - UriResourceLocatorStateless.getChatResourceUri(config)); + // swap it to ollama, check we still get the chatCompletionsUri, as its the openai + // compat endpoint we use. + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("OLLAMA"); + Assert.assertEquals( + UriResourceLocatorStateless.chatCompletionsUri(), + UriResourceLocatorStateless.getChatResourceUri(config)); - // finally change to GENERIC, and check that we can specify any endpoint - when(globalConfig.getString(Mockito.eq(KEY_AI_TYPE), Mockito.anyString())) - .thenReturn("GENERIC"); + // finally change to GENERIC, and check that we can specify any endpoint + when(globalConfig.getString(Mockito.eq(KEY_AI_TYPE), Mockito.anyString())) + .thenReturn("GENERIC"); - final String expectedValueForEndpoint = "/someendpoint/someapi/chat"; - when(globalConfig.getString(Mockito.eq(KEY_AI_CHAT_ENDPOINT), Mockito.anyString())) - .thenReturn(expectedValueForEndpoint); - Assert.assertEquals(expectedValueForEndpoint, - UriResourceLocatorStateless.getChatResourceUri(config)); - } + final String expectedValueForEndpoint = "/someendpoint/someapi/chat"; + when(globalConfig.getString(Mockito.eq(KEY_AI_CHAT_ENDPOINT), Mockito.anyString())) + .thenReturn(expectedValueForEndpoint); + Assert.assertEquals( + expectedValueForEndpoint, UriResourceLocatorStateless.getChatResourceUri(config)); + } - @Test - public void testAITypeControlsAuthHeader(){ - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("CHATGPT"); + @Test + public void testAITypeControlsAuthHeader() { + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("CHATGPT"); - // check default for aiType is chatGPT. - Assert.assertEquals("Authorization", config.getAuthorizationHeaderInfo().getName()); - Assert.assertEquals("Bearer " + config.getAIToken(), config.getAuthorizationHeaderInfo().getValue()); + // check default for aiType is chatGPT. + Assert.assertEquals("Authorization", config.getAuthorizationHeaderInfo().getName()); + Assert.assertEquals( + "Bearer " + config.getAIToken(), config.getAuthorizationHeaderInfo().getValue()); - // swap it to ollama, check we still get the chatCompletionsUri, as its the openai - // compat endpoint we use. - when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())) - .thenReturn("OLLAMA"); - Assert.assertNull("No expected value for auth header for ollama", config.getAuthorizationHeaderInfo()); - } + // swap it to ollama, check we still get the chatCompletionsUri, as its the openai + // compat endpoint we use. + when(globalConfig.getString(Mockito.eq("aiType"), Mockito.anyString())).thenReturn("OLLAMA"); + Assert.assertNull( + "No expected value for auth header for ollama", config.getAuthorizationHeaderInfo()); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewTestBase.java index 1d81253..5e4b6c7 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewTestBase.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatReviewTestBase.java
@@ -1,9 +1,15 @@ package com.googlesource.gerrit.plugins.aicodereview; +import static com.google.gerrit.extensions.client.ChangeKind.REWORK; +import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.EVENT_CLASS_MAP; +import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.google.gerrit.entities.Account; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.account.AccountState; import com.google.gerrit.extensions.api.GerritApi; import com.google.gerrit.extensions.api.accounts.AccountApi; import com.google.gerrit.extensions.api.accounts.Accounts; @@ -14,6 +20,8 @@ import com.google.gerrit.extensions.common.GroupInfo; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.json.OutputFormat; +import com.google.gerrit.server.account.AccountCache; +import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.data.AccountAttribute; import com.google.gerrit.server.data.PatchSetAttribute; @@ -29,8 +37,8 @@ import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; -import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi.ChatAIClient; import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.gerrit.GerritClientPatchSet; +import com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi.ChatAIClient; import com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask; import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritClient; @@ -43,14 +51,6 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.api.git.GitRepoFiles; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.chatai.AIChatClientStateless; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api.gerrit.GerritClientPatchSetStateless; -import lombok.NonNull; -import org.junit.Before; -import org.junit.Rule; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.stubbing.Answer; - import java.io.IOException; import java.lang.reflect.Type; import java.nio.file.Files; @@ -59,345 +59,336 @@ import java.time.Instant; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; - -import static com.google.gerrit.extensions.client.ChangeKind.REWORK; -import static com.googlesource.gerrit.plugins.aicodereview.listener.EventHandlerTask.EVENT_CLASS_MAP; -import static com.googlesource.gerrit.plugins.aicodereview.utils.GsonUtils.getGson; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import lombok.NonNull; +import org.junit.Before; +import org.junit.Rule; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; public class AIChatReviewTestBase extends AIChatTestBase { - protected static final Path basePath = Paths.get("src/test/resources"); - public static final int GERRIT_GPT_ACCOUNT_ID = 1000000; - public static final String GERRIT_GPT_USERNAME = "gpt"; - public static final int GERRIT_USER_ACCOUNT_ID = 1000001; - protected static final String GERRIT_USER_ACCOUNT_NAME = "Test"; - protected static final String GERRIT_USER_ACCOUNT_EMAIL = "test@example.com"; - protected static final String GERRIT_USER_USERNAME = "test"; - protected static final String GERRIT_USER_GROUP = "Test"; - protected static final String AI_TOKEN = "tk-test"; - protected static final String GPT_DOMAIN = "http://localhost:9527"; - protected static final boolean GPT_STREAM_OUTPUT = true; - protected static final long TEST_TIMESTAMP = 1699270812; - private static final int GPT_USER_ACCOUNT_ID = 1000000; + protected static final Path basePath = Paths.get("src/test/resources"); + public static final int GERRIT_GPT_ACCOUNT_ID = 1000000; + public static final String GERRIT_GPT_USERNAME = "gpt"; + public static final int GERRIT_USER_ACCOUNT_ID = 1000001; + protected static final String GERRIT_USER_ACCOUNT_NAME = "Test"; + protected static final String GERRIT_USER_ACCOUNT_EMAIL = "test@example.com"; + protected static final String GERRIT_USER_USERNAME = "test"; + protected static final String GERRIT_USER_GROUP = "Test"; + protected static final String AI_TOKEN = "tk-test"; + protected static final String GPT_DOMAIN = "http://localhost:9527"; + protected static final boolean GPT_STREAM_OUTPUT = true; + protected static final long TEST_TIMESTAMP = 1699270812; + private static final int GPT_USER_ACCOUNT_ID = 1000000; - @Rule - public WireMockRule wireMockRule = new WireMockRule(9527); + @Rule public WireMockRule wireMockRule = new WireMockRule(9527); - @Mock - protected GitRepoFiles gitRepoFiles; + @Mock protected GitRepoFiles gitRepoFiles; - @Mock - protected PluginDataHandlerProvider pluginDataHandlerProvider; + @Mock protected PluginDataHandlerProvider pluginDataHandlerProvider; - @Mock - protected PluginDataHandler pluginDataHandler; + @Mock protected PluginDataHandler pluginDataHandler; - @Mock - protected OneOffRequestContext context; - @Mock - protected GerritApi gerritApi; - @Mock - protected Changes changesMock; - @Mock - protected ChangeApi changeApiMock; - @Mock - protected RevisionApi revisionApiMock; - @Mock - protected ReviewResult reviewResult; - @Mock - protected CommentsRequest commentsRequestMock; - @Mock - protected AccountCache accountCacheMock; + @Mock protected OneOffRequestContext context; + @Mock protected GerritApi gerritApi; + @Mock protected Changes changesMock; + @Mock protected ChangeApi changeApiMock; + @Mock protected RevisionApi revisionApiMock; + @Mock protected ReviewResult reviewResult; + @Mock protected CommentsRequest commentsRequestMock; + @Mock protected AccountCache accountCacheMock; - protected PluginConfig globalConfig; - protected PluginConfig projectConfig; - protected Configuration config; - protected ChangeSetData changeSetData; - protected GerritClient gerritClient; - protected PatchSetReviewer patchSetReviewer; - protected ConfigCreator mockConfigCreator; - protected JsonObject gptRequestBody; - protected String promptTagComments; + protected PluginConfig globalConfig; + protected PluginConfig projectConfig; + protected Configuration config; + protected ChangeSetData changeSetData; + protected GerritClient gerritClient; + protected PatchSetReviewer patchSetReviewer; + protected ConfigCreator mockConfigCreator; + protected JsonObject gptRequestBody; + protected String promptTagComments; - @Before - public void before() throws RestApiException { - initGlobalAndProjectConfig(); - initConfig(); - setupMockRequests(); - initComparisonContent(); - initTest(); - } + @Before + public void before() throws RestApiException { + initGlobalAndProjectConfig(); + initConfig(); + setupMockRequests(); + initComparisonContent(); + initTest(); + } - protected void initGlobalAndProjectConfig() { - globalConfig = mock(PluginConfig.class); - Answer<Object> returnDefaultArgument = invocation -> { - // Return the second argument (i.e., the Default value) passed to the method - return invocation.getArgument(1); + protected void initGlobalAndProjectConfig() { + globalConfig = mock(PluginConfig.class); + Answer<Object> returnDefaultArgument = + invocation -> { + // Return the second argument (i.e., the Default value) passed to the method + return invocation.getArgument(1); }; - // Mock the Global Config values not provided by Default - when(globalConfig.getString("aiToken")).thenReturn(AI_TOKEN); + // Mock the Global Config values not provided by Default + when(globalConfig.getString("aiToken")).thenReturn(AI_TOKEN); - // Mock the Global Config values to the Defaults passed as second arguments of the `get*` methods. - when(globalConfig.getString(Mockito.anyString(), Mockito.anyString())).thenAnswer(returnDefaultArgument); - when(globalConfig.getInt(Mockito.anyString(), Mockito.anyInt())).thenAnswer(returnDefaultArgument); - when(globalConfig.getBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenAnswer(returnDefaultArgument); + // Mock the Global Config values to the Defaults passed as second arguments of the `get*` + // methods. + when(globalConfig.getString(Mockito.anyString(), Mockito.anyString())) + .thenAnswer(returnDefaultArgument); + when(globalConfig.getInt(Mockito.anyString(), Mockito.anyInt())) + .thenAnswer(returnDefaultArgument); + when(globalConfig.getBoolean(Mockito.anyString(), Mockito.anyBoolean())) + .thenAnswer(returnDefaultArgument); - // Mock the Global Config values that differ from the ones provided by Default - when(globalConfig.getString(Mockito.eq("aiDomain"), Mockito.anyString())) - .thenReturn(GPT_DOMAIN); - when(globalConfig.getString("gerritUserName")).thenReturn(GERRIT_GPT_USERNAME); + // Mock the Global Config values that differ from the ones provided by Default + when(globalConfig.getString(Mockito.eq("aiDomain"), Mockito.anyString())) + .thenReturn(GPT_DOMAIN); + when(globalConfig.getString("gerritUserName")).thenReturn(GERRIT_GPT_USERNAME); - projectConfig = mock(PluginConfig.class); + projectConfig = mock(PluginConfig.class); - // Mock the Project Config values - when(projectConfig.getBoolean(Mockito.eq("isEnabled"), Mockito.anyBoolean())).thenReturn(true); + // Mock the Project Config values + when(projectConfig.getBoolean(Mockito.eq("isEnabled"), Mockito.anyBoolean())).thenReturn(true); + } + + protected void initConfig() { + config = + new Configuration( + context, gerritApi, globalConfig, projectConfig, "gpt@email.com", Account.id(1000000)); + } + + protected void setupMockRequests() throws RestApiException { + Accounts accountsMock = mockGerritAccountsRestEndpoint(); + // Mock the behavior of the gerritAccountIdUri request + mockGerritAccountsQueryApiCall(GERRIT_GPT_USERNAME, GERRIT_GPT_ACCOUNT_ID); + + // Mock the behavior of the gerritAccountIdUri request + mockGerritAccountsQueryApiCall(GERRIT_USER_USERNAME, GERRIT_USER_ACCOUNT_ID); + + // Mock the behavior of the gerritAccountGroups request + mockGerritAccountGroupsApiCall(accountsMock, GERRIT_USER_ACCOUNT_ID); + + mockGerritChangeApiRestEndpoint(); + + // Mock the behavior of the gerritGetPatchSetDetailUri request + mockGerritChangeDetailsApiCall(); + + // Mock the behavior of the gerritPatchSet comments request + mockGerritChangeCommentsApiCall(); + + // Mock the behavior of the gerrit Review request + mockGerritReviewApiCall(); + + // Mock the GerritApi's revision API + when(changeApiMock.current()).thenReturn(revisionApiMock); + + // Mock the pluginDataHandlerProvider to return the mocked Change pluginDataHandler + when(pluginDataHandlerProvider.getChangeScope()).thenReturn(pluginDataHandler); + } + + private Accounts mockGerritAccountsRestEndpoint() { + Accounts accountsMock = mock(Accounts.class); + when(gerritApi.accounts()).thenReturn(accountsMock); + return accountsMock; + } + + private void mockGerritAccountsQueryApiCall(String username, int expectedAccountId) { + AccountState accountStateMock = mock(AccountState.class); + Account accountMock = mock(Account.class); + when(accountStateMock.account()).thenReturn(accountMock); + when(accountMock.id()).thenReturn(Account.id(expectedAccountId)); + when(accountCacheMock.getByUsername(username)).thenReturn(Optional.of(accountStateMock)); + } + + private void mockGerritAccountGroupsApiCall(Accounts accountsMock, int accountId) + throws RestApiException { + Gson gson = OutputFormat.JSON.newGson(); + List<GroupInfo> groups = + gson.fromJson( + readTestFile("__files/gerritAccountGroups.json"), + new TypeLiteral<List<GroupInfo>>() {}.getType()); + AccountApi accountApiMock = mock(AccountApi.class); + when(accountsMock.id(accountId)).thenReturn(accountApiMock); + when(accountApiMock.getGroups()).thenReturn(groups); + } + + private void mockGerritChangeDetailsApiCall() throws RestApiException { + ChangeInfo changeInfo = + readTestFileToClass("__files/gerritPatchSetDetail.json", ChangeInfo.class); + when(changeApiMock.get()).thenReturn(changeInfo); + } + + private void mockGerritChangeCommentsApiCall() throws RestApiException { + Map<String, List<CommentInfo>> comments = + readTestFileToType( + "__files/gerritPatchSetComments.json", + new TypeLiteral<Map<String, List<CommentInfo>>>() {}.getType()); + when(changeApiMock.commentsRequest()).thenReturn(commentsRequestMock); + when(commentsRequestMock.get()).thenReturn(comments); + } + + private void mockGerritChangeApiRestEndpoint() throws RestApiException { + when(gerritApi.changes()).thenReturn(changesMock); + when(changesMock.id(PROJECT_NAME.get(), BRANCH_NAME.shortName(), CHANGE_ID.get())) + .thenReturn(changeApiMock); + } + + private void mockGerritReviewApiCall() throws RestApiException { + ArgumentCaptor<ReviewInput> reviewInputCaptor = ArgumentCaptor.forClass(ReviewInput.class); + when(revisionApiMock.review(reviewInputCaptor.capture())).thenReturn(reviewResult); + } + + protected void initComparisonContent() {} + + protected <T> T readTestFileToClass(String filename, Class<T> clazz) { + Gson gson = OutputFormat.JSON.newGson(); + return gson.fromJson(readTestFile(filename), clazz); + } + + protected <T> T readTestFileToType(String filename, Type type) { + Gson gson = OutputFormat.JSON.newGson(); + return gson.fromJson(readTestFile(filename), type); + } + + protected String readTestFile(String filename) { + try { + return new String(Files.readAllBytes(basePath.resolve(filename))); + } catch (IOException e) { + throw new RuntimeException(e); } + } - protected void initConfig() { - config = new Configuration( - context, - gerritApi, - globalConfig, - projectConfig, - "gpt@email.com", - Account.id(1000000) - ); - } + protected EventHandlerTask.Result handleEventBasedOnType( + EventHandlerTask.SupportedEvents triggeredEvent) { + Consumer<Event> typeSpecificSetup = getTypeSpecificSetup(triggeredEvent); + Event event = getMockedEvent(triggeredEvent); + setupCommonEventMocks((PatchSetEvent) event); // Apply common mock configurations + typeSpecificSetup.accept(event); - protected void setupMockRequests() throws RestApiException { - Accounts accountsMock = mockGerritAccountsRestEndpoint(); - // Mock the behavior of the gerritAccountIdUri request - mockGerritAccountsQueryApiCall(GERRIT_GPT_USERNAME, GERRIT_GPT_ACCOUNT_ID); + EventHandlerTask task = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + install(new TestGerritEventContextModule(config, event)); - // Mock the behavior of the gerritAccountIdUri request - mockGerritAccountsQueryApiCall(GERRIT_USER_USERNAME, GERRIT_USER_ACCOUNT_ID); + bind(GerritClient.class).toInstance(gerritClient); + bind(GitRepoFiles.class).toInstance(gitRepoFiles); + bind(ConfigCreator.class).toInstance(mockConfigCreator); + bind(PatchSetReviewer.class).toInstance(patchSetReviewer); + bind(PluginDataHandlerProvider.class).toInstance(pluginDataHandlerProvider); + bind(AccountCache.class).toInstance(mockAccountCache()); + } + }) + .getInstance(EventHandlerTask.class); + return task.execute(); + } - // Mock the behavior of the gerritAccountGroups request - mockGerritAccountGroupsApiCall(accountsMock, GERRIT_USER_ACCOUNT_ID); + protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { + ArgumentCaptor<ReviewInput> reviewInputCaptor = ArgumentCaptor.forClass(ReviewInput.class); + verify(revisionApiMock).review(reviewInputCaptor.capture()); + gptRequestBody = + getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(), JsonObject.class); + return reviewInputCaptor; + } - mockGerritChangeApiRestEndpoint(); - - // Mock the behavior of the gerritGetPatchSetDetailUri request - mockGerritChangeDetailsApiCall(); - - // Mock the behavior of the gerritPatchSet comments request - mockGerritChangeCommentsApiCall(); - - // Mock the behavior of the gerrit Review request - mockGerritReviewApiCall(); - - // Mock the GerritApi's revision API - when(changeApiMock.current()).thenReturn(revisionApiMock); - - // Mock the pluginDataHandlerProvider to return the mocked Change pluginDataHandler - when(pluginDataHandlerProvider.getChangeScope()).thenReturn(pluginDataHandler); - } - - private Accounts mockGerritAccountsRestEndpoint() { - Accounts accountsMock = mock(Accounts.class); - when(gerritApi.accounts()).thenReturn(accountsMock); - return accountsMock; - } - - private void mockGerritAccountsQueryApiCall(String username, int expectedAccountId) { - AccountState accountStateMock = mock(AccountState.class); - Account accountMock = mock(Account.class); - when(accountStateMock.account()).thenReturn(accountMock); - when(accountMock.id()).thenReturn(Account.id(expectedAccountId)); - when(accountCacheMock.getByUsername(username)).thenReturn(Optional.of(accountStateMock)); - } - - private void mockGerritAccountGroupsApiCall(Accounts accountsMock, int accountId) - throws RestApiException { - Gson gson = OutputFormat.JSON.newGson(); - List<GroupInfo> groups = - gson.fromJson( - readTestFile("__files/gerritAccountGroups.json"), - new TypeLiteral<List<GroupInfo>>() {}.getType()); - AccountApi accountApiMock = mock(AccountApi.class); - when(accountsMock.id(accountId)).thenReturn(accountApiMock); - when(accountApiMock.getGroups()).thenReturn(groups); - } - - private void mockGerritChangeDetailsApiCall() throws RestApiException { - ChangeInfo changeInfo = readTestFileToClass("__files/gerritPatchSetDetail.json", ChangeInfo.class); - when(changeApiMock.get()).thenReturn(changeInfo); - } - - private void mockGerritChangeCommentsApiCall() throws RestApiException { - Map<String, List<CommentInfo>> comments = - readTestFileToType( - "__files/gerritPatchSetComments.json", - new TypeLiteral<Map<String, List<CommentInfo>>>() {}.getType()); - when(changeApiMock.commentsRequest()).thenReturn(commentsRequestMock); - when(commentsRequestMock.get()).thenReturn(comments); - } - - private void mockGerritChangeApiRestEndpoint() throws RestApiException { - when(gerritApi.changes()).thenReturn(changesMock); - when(changesMock.id(PROJECT_NAME.get(), BRANCH_NAME.shortName(), CHANGE_ID.get())).thenReturn(changeApiMock); - } - - private void mockGerritReviewApiCall() throws RestApiException { - ArgumentCaptor<ReviewInput> reviewInputCaptor = ArgumentCaptor.forClass(ReviewInput.class); - when(revisionApiMock.review(reviewInputCaptor.capture())).thenReturn(reviewResult); - } - - protected void initComparisonContent() {} - - protected <T> T readTestFileToClass(String filename, Class<T> clazz) { - Gson gson = OutputFormat.JSON.newGson(); - return gson.fromJson(readTestFile(filename), clazz); - } - - protected <T> T readTestFileToType(String filename, Type type) { - Gson gson = OutputFormat.JSON.newGson(); - return gson.fromJson(readTestFile(filename), type); - } - - protected String readTestFile(String filename) { - try { - return new String(Files.readAllBytes(basePath.resolve(filename))); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected EventHandlerTask.Result handleEventBasedOnType(EventHandlerTask.SupportedEvents triggeredEvent) { - Consumer<Event> typeSpecificSetup = getTypeSpecificSetup(triggeredEvent); - Event event = getMockedEvent(triggeredEvent); - setupCommonEventMocks((PatchSetEvent) event); // Apply common mock configurations - typeSpecificSetup.accept(event); - - EventHandlerTask task = Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - install(new TestGerritEventContextModule(config, event)); - - bind(GerritClient.class).toInstance(gerritClient); - bind(GitRepoFiles.class).toInstance(gitRepoFiles); - bind(ConfigCreator.class).toInstance(mockConfigCreator); - bind(PatchSetReviewer.class).toInstance(patchSetReviewer); - bind(PluginDataHandlerProvider.class).toInstance(pluginDataHandlerProvider); - bind(AccountCache.class).toInstance(mockAccountCache()); - } - }).getInstance(EventHandlerTask.class); - return task.execute(); - } - - protected ArgumentCaptor<ReviewInput> testRequestSent() throws RestApiException { - ArgumentCaptor<ReviewInput> reviewInputCaptor = ArgumentCaptor.forClass(ReviewInput.class); - verify(revisionApiMock).review(reviewInputCaptor.capture()); - gptRequestBody = getGson().fromJson(patchSetReviewer.getChatGptClient().getRequestBody(), JsonObject.class); - return reviewInputCaptor; - } - - protected void initTest() { - changeSetData = new ChangeSetData( - GPT_USER_ACCOUNT_ID, - config.getVotingMinScore(), - config.getMaxReviewFileSize() - ); - Localizer localizer = new Localizer(config); - gerritClient = - new GerritClient( - new GerritClientFacade( - config, - changeSetData, - new GerritClientComments( - config, - accountCacheMock, - changeSetData, - pluginDataHandlerProvider, - localizer - ), - getGerritClientPatchSet())); - patchSetReviewer = - new PatchSetReviewer( - gerritClient, + protected void initTest() { + changeSetData = + new ChangeSetData( + GPT_USER_ACCOUNT_ID, config.getVotingMinScore(), config.getMaxReviewFileSize()); + Localizer localizer = new Localizer(config); + gerritClient = + new GerritClient( + new GerritClientFacade( config, changeSetData, - Providers.of(new GerritClientReview(config, accountCacheMock, pluginDataHandlerProvider, localizer)), - getChatGptClient(), - localizer - ); - mockConfigCreator = mock(ConfigCreator.class); - } + new GerritClientComments( + config, accountCacheMock, changeSetData, pluginDataHandlerProvider, localizer), + getGerritClientPatchSet())); + patchSetReviewer = + new PatchSetReviewer( + gerritClient, + config, + changeSetData, + Providers.of( + new GerritClientReview( + config, accountCacheMock, pluginDataHandlerProvider, localizer)), + getChatGptClient(), + localizer); + mockConfigCreator = mock(ConfigCreator.class); + } - private AccountAttribute createTestAccountAttribute() { - AccountAttribute accountAttribute = new AccountAttribute(); - accountAttribute.name = GERRIT_USER_ACCOUNT_NAME; - accountAttribute.username = GERRIT_USER_USERNAME; - accountAttribute.email = GERRIT_USER_ACCOUNT_EMAIL; - return accountAttribute; - } + private AccountAttribute createTestAccountAttribute() { + AccountAttribute accountAttribute = new AccountAttribute(); + accountAttribute.name = GERRIT_USER_ACCOUNT_NAME; + accountAttribute.username = GERRIT_USER_USERNAME; + accountAttribute.email = GERRIT_USER_ACCOUNT_EMAIL; + return accountAttribute; + } - private PatchSetAttribute createPatchSetAttribute() { - PatchSetAttribute patchSetAttribute = new PatchSetAttribute(); - patchSetAttribute.kind = REWORK; - patchSetAttribute.author = createTestAccountAttribute(); - return patchSetAttribute; - } + private PatchSetAttribute createPatchSetAttribute() { + PatchSetAttribute patchSetAttribute = new PatchSetAttribute(); + patchSetAttribute.kind = REWORK; + patchSetAttribute.author = createTestAccountAttribute(); + return patchSetAttribute; + } - @NonNull - private Consumer<Event> getTypeSpecificSetup(EventHandlerTask.SupportedEvents triggeredEvent) { - return switch (triggeredEvent) { - case COMMENT_ADDED -> event -> { - CommentAddedEvent commentEvent = (CommentAddedEvent) event; - commentEvent.author = this::createTestAccountAttribute; - commentEvent.patchSet = this::createPatchSetAttribute; - commentEvent.eventCreatedOn = TEST_TIMESTAMP; - when(commentEvent.getType()).thenReturn("comment-added"); - }; - case PATCH_SET_CREATED -> event -> { - PatchSetCreatedEvent patchEvent = (PatchSetCreatedEvent) event; - patchEvent.patchSet = this::createPatchSetAttribute; - when(patchEvent.getType()).thenReturn("patchset-created"); - }; - case CHANGE_MERGED -> event -> { - ChangeMergedEvent mergedEvent = (ChangeMergedEvent) event; - when(mergedEvent.getType()).thenReturn("change-merged"); - }; - }; - } + @NonNull + private Consumer<Event> getTypeSpecificSetup(EventHandlerTask.SupportedEvents triggeredEvent) { + return switch (triggeredEvent) { + case COMMENT_ADDED -> + event -> { + CommentAddedEvent commentEvent = (CommentAddedEvent) event; + commentEvent.author = this::createTestAccountAttribute; + commentEvent.patchSet = this::createPatchSetAttribute; + commentEvent.eventCreatedOn = TEST_TIMESTAMP; + when(commentEvent.getType()).thenReturn("comment-added"); + }; + case PATCH_SET_CREATED -> + event -> { + PatchSetCreatedEvent patchEvent = (PatchSetCreatedEvent) event; + patchEvent.patchSet = this::createPatchSetAttribute; + when(patchEvent.getType()).thenReturn("patchset-created"); + }; + case CHANGE_MERGED -> + event -> { + ChangeMergedEvent mergedEvent = (ChangeMergedEvent) event; + when(mergedEvent.getType()).thenReturn("change-merged"); + }; + }; + } - private Event getMockedEvent(EventHandlerTask.SupportedEvents triggeredEvent) { - return (Event) mock(EVENT_CLASS_MAP.get(triggeredEvent)); - } + private Event getMockedEvent(EventHandlerTask.SupportedEvents triggeredEvent) { + return (Event) mock(EVENT_CLASS_MAP.get(triggeredEvent)); + } - private void setupCommonEventMocks(PatchSetEvent event) { - when(event.getProjectNameKey()).thenReturn(PROJECT_NAME); - when(event.getBranchNameKey()).thenReturn(BRANCH_NAME); - when(event.getChangeKey()).thenReturn(CHANGE_ID); - } + private void setupCommonEventMocks(PatchSetEvent event) { + when(event.getProjectNameKey()).thenReturn(PROJECT_NAME); + when(event.getBranchNameKey()).thenReturn(BRANCH_NAME); + when(event.getChangeKey()).thenReturn(CHANGE_ID); + } - private AccountCache mockAccountCache() { - AccountCache accountCache = mock(AccountCache.class); - Account account = Account.builder(Account.id(GPT_USER_ACCOUNT_ID), Instant.now()).build(); - AccountState accountState = AccountState.forAccount(account, Collections.emptyList()); - doReturn(Optional.of(accountState)).when(accountCache).getByUsername(GERRIT_GPT_USERNAME); + private AccountCache mockAccountCache() { + AccountCache accountCache = mock(AccountCache.class); + Account account = Account.builder(Account.id(GPT_USER_ACCOUNT_ID), Instant.now()).build(); + AccountState accountState = AccountState.forAccount(account, Collections.emptyList()); + doReturn(Optional.of(accountState)).when(accountCache).getByUsername(GERRIT_GPT_USERNAME); - return accountCache; - } + return accountCache; + } - private ChatAIClient getChatGptClient() { - return switch (config.getAIMode()) { - case stateful -> new AIChatClientStateful(config, gitRepoFiles, pluginDataHandlerProvider); - case stateless -> new AIChatClientStateless(config); - }; - } + private ChatAIClient getChatGptClient() { + return switch (config.getAIMode()) { + case stateful -> new AIChatClientStateful(config, gitRepoFiles, pluginDataHandlerProvider); + case stateless -> new AIChatClientStateless(config); + }; + } - private GerritClientPatchSet getGerritClientPatchSet() { - return switch (config.getAIMode()) { - case stateful -> new GerritClientPatchSetStateful(config, accountCacheMock); - case stateless -> new GerritClientPatchSetStateless(config, accountCacheMock); - }; - } + private GerritClientPatchSet getGerritClientPatchSet() { + return switch (config.getAIMode()) { + case stateful -> new GerritClientPatchSetStateful(config, accountCacheMock); + case stateless -> new GerritClientPatchSetStateless(config, accountCacheMock); + }; + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatTestBase.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatTestBase.java index 7e6aff2..fd9524c 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatTestBase.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/AIChatTestBase.java
@@ -1,39 +1,38 @@ package com.googlesource.gerrit.plugins.aicodereview; +import static org.mockito.Mockito.when; + import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Project; import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange; +import java.nio.file.Path; import org.junit.Rule; import org.junit.rules.TemporaryFolder; import org.mockito.Mock; -import java.nio.file.Path; - -import static org.mockito.Mockito.when; - public class AIChatTestBase { - protected static final Project.NameKey PROJECT_NAME = Project.NameKey.parse("myProject"); - protected static final Change.Key CHANGE_ID = Change.Key.parse("myChangeId"); - protected static final BranchNameKey BRANCH_NAME = BranchNameKey.create(PROJECT_NAME, "myBranchName"); + protected static final Project.NameKey PROJECT_NAME = Project.NameKey.parse("myProject"); + protected static final Change.Key CHANGE_ID = Change.Key.parse("myChangeId"); + protected static final BranchNameKey BRANCH_NAME = + BranchNameKey.create(PROJECT_NAME, "myBranchName"); - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - @Mock - protected Path mockPluginDataPath; + @Mock protected Path mockPluginDataPath; - protected Path realPluginDataPath; + protected Path realPluginDataPath; - protected void setupPluginData() { - realPluginDataPath = tempFolder.getRoot().toPath().resolve("global.data"); - Path realProjectDataPath = tempFolder.getRoot().toPath().resolve(PROJECT_NAME + ".data"); + protected void setupPluginData() { + realPluginDataPath = tempFolder.getRoot().toPath().resolve("global.data"); + Path realProjectDataPath = tempFolder.getRoot().toPath().resolve(PROJECT_NAME + ".data"); - // Mock the PluginData annotation project behavior - when(mockPluginDataPath.resolve(PROJECT_NAME + ".data")).thenReturn(realProjectDataPath); - } + // Mock the PluginData annotation project behavior + when(mockPluginDataPath.resolve(PROJECT_NAME + ".data")).thenReturn(realProjectDataPath); + } - protected GerritChange getGerritChange() { - return new GerritChange(AIChatTestBase.PROJECT_NAME, AIChatTestBase.BRANCH_NAME, AIChatTestBase.CHANGE_ID); - } + protected GerritChange getGerritChange() { + return new GerritChange( + AIChatTestBase.PROJECT_NAME, AIChatTestBase.BRANCH_NAME, AIChatTestBase.CHANGE_ID); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/PluginDataTest.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/PluginDataTest.java index 5fee984..4ceb349 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/PluginDataTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/PluginDataTest.java
@@ -1,12 +1,13 @@ package com.googlesource.gerrit.plugins.aicodereview; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; -import java.nio.file.Files; - import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandler; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; +import java.nio.file.Files; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -15,58 +16,65 @@ @RunWith(MockitoJUnitRunner.class) public class PluginDataTest extends AIChatTestBase { - @Before - public void setUp() { - setupPluginData(); + @Before + public void setUp() { + setupPluginData(); - // Mock the PluginData annotation global behavior - when(mockPluginDataPath.resolve("global.data")).thenReturn(realPluginDataPath); - } + // Mock the PluginData annotation global behavior + when(mockPluginDataPath.resolve("global.data")).thenReturn(realPluginDataPath); + } - @Test - public void testValueSetAndGet() { - PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); - PluginDataHandler globalHandler = provider.getGlobalScope(); - PluginDataHandler projectHandler = provider.getProjectScope(); + @Test + public void testValueSetAndGet() { + PluginDataHandlerProvider provider = + new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); + PluginDataHandler globalHandler = provider.getGlobalScope(); + PluginDataHandler projectHandler = provider.getProjectScope(); - String key = "testKey"; - String value = "testValue"; + String key = "testKey"; + String value = "testValue"; - // Test set value - globalHandler.setValue(key, value); - projectHandler.setValue(key, value); + // Test set value + globalHandler.setValue(key, value); + projectHandler.setValue(key, value); - // Test get value - assertEquals("The value retrieved should match the value set.", value, globalHandler.getValue(key)); - assertEquals("The value retrieved should match the value set.", value, projectHandler.getValue(key)); - } + // Test get value + assertEquals( + "The value retrieved should match the value set.", value, globalHandler.getValue(key)); + assertEquals( + "The value retrieved should match the value set.", value, projectHandler.getValue(key)); + } - @Test - public void testRemoveValue() { - PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); - PluginDataHandler handler = provider.getGlobalScope(); + @Test + public void testRemoveValue() { + PluginDataHandlerProvider provider = + new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); + PluginDataHandler handler = provider.getGlobalScope(); - String key = "testKey"; - String value = "testValue"; + String key = "testKey"; + String value = "testValue"; - // Set a value to ensure it can be removed - handler.setValue(key, value); - // Remove the value - handler.removeValue(key); + // Set a value to ensure it can be removed + handler.setValue(key, value); + // Remove the value + handler.removeValue(key); - // Verify the value is no longer available - assertNull("The value should be null after being removed.", handler.getValue(key)); - } + // Verify the value is no longer available + assertNull("The value should be null after being removed.", handler.getValue(key)); + } - @Test - public void testCreateFileOnNonexistent() throws Exception { - // Ensure the file doesn't exist before creating the handler - Files.deleteIfExists(realPluginDataPath); + @Test + public void testCreateFileOnNonexistent() throws Exception { + // Ensure the file doesn't exist before creating the handler + Files.deleteIfExists(realPluginDataPath); - PluginDataHandlerProvider provider = new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); - provider.getGlobalScope(); + PluginDataHandlerProvider provider = + new PluginDataHandlerProvider(mockPluginDataPath, getGerritChange()); + provider.getGlobalScope(); - // The constructor should create the file if it doesn't exist - assertTrue("The config file should exist after initializing the handler.", Files.exists(realPluginDataPath)); - } + // The constructor should create the file if it doesn't exist + assertTrue( + "The config file should exist after initializing the handler.", + Files.exists(realPluginDataPath)); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TemporaryFileTest.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TemporaryFileTest.java index 30be370..95fae77 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TemporaryFileTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TemporaryFileTest.java
@@ -1,35 +1,34 @@ package com.googlesource.gerrit.plugins.aicodereview; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; - -import com.googlesource.gerrit.plugins.aicodereview.utils.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class TemporaryFileTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - @Test - public void testCreateTempFileWithContent() throws IOException { - // Prepare - String prefix = "testFile"; - String suffix = ".txt"; - String content = "This is a test content"; + @Test + public void testCreateTempFileWithContent() throws IOException { + // Prepare + String prefix = "testFile"; + String suffix = ".txt"; + String content = "This is a test content"; - // Execute - Path tempFile = FileUtils.createTempFileWithContent(prefix, suffix, content); + // Execute + Path tempFile = FileUtils.createTempFileWithContent(prefix, suffix, content); - // Verify the file exists - assertTrue("Temporary file should exist", Files.exists(tempFile)); + // Verify the file exists + assertTrue("Temporary file should exist", Files.exists(tempFile)); - // Read back the content - String fileContent = Files.readString(tempFile); - assertEquals("File content should match", content, fileContent); - } + // Read back the content + String fileContent = Files.readString(tempFile); + assertEquals("File content should match", content, fileContent); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java index 47dd921..2662d5f 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java
@@ -5,19 +5,18 @@ import com.google.inject.Provides; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.listener.GerritEventContextModule; - import java.nio.file.Path; import java.nio.file.Paths; public class TestGerritEventContextModule extends GerritEventContextModule { - public TestGerritEventContextModule(Configuration config, Event event) { - super(config, event); - } + public TestGerritEventContextModule(Configuration config, Event event) { + super(config, event); + } - @Provides - @PluginData - Path providePluginDataPath() { - return Paths.get(System.getProperty("pluginDataPath", "test-plugin-data")); - } + @Provides + @PluginData + Path providePluginDataPath() { + return Paths.get(System.getProperty("pluginDataPath", "test-plugin-data")); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/integration/CodeReviewPluginIT.java b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/integration/CodeReviewPluginIT.java index f196ce4..1ad3e1c 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/integration/CodeReviewPluginIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/integration/CodeReviewPluginIT.java
@@ -1,5 +1,8 @@ package com.googlesource.gerrit.plugins.aicodereview.integration; +import static junit.framework.TestCase.assertNotNull; +import static org.mockito.Mockito.when; + import com.google.gerrit.server.account.AccountCache; import com.googlesource.gerrit.plugins.aicodereview.config.Configuration; import com.googlesource.gerrit.plugins.aicodereview.data.PluginDataHandlerProvider; @@ -12,6 +15,8 @@ import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data.ChangeSetData; import com.googlesource.gerrit.plugins.aicodereview.mode.common.model.review.ReviewBatch; import com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.prompt.AIChatPromptStateless; +import java.util.ArrayList; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.junit.Ignore; import org.junit.Test; @@ -20,65 +25,59 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.ArrayList; -import java.util.List; - -import static junit.framework.TestCase.assertNotNull; -import static org.mockito.Mockito.when; - -@Ignore("This test suite is designed to demonstrate how to test the Gerrit and GPT interfaces in a real environment. " + - "It is not intended to be executed during the regular build process") +@Ignore( + "This test suite is designed to demonstrate how to test the Gerrit and GPT interfaces in a real" + + " environment. It is not intended to be executed during the regular build process") @Slf4j @RunWith(MockitoJUnitRunner.class) public class CodeReviewPluginIT { - @Mock - private Configuration config; + @Mock private Configuration config; - @Mock - protected PluginDataHandlerProvider pluginDataHandlerProvider; + @Mock protected PluginDataHandlerProvider pluginDataHandlerProvider; - @InjectMocks - private GerritClient gerritClient; + @InjectMocks private GerritClient gerritClient; - @InjectMocks - private ChatAIClient chatGptClient; + @InjectMocks private ChatAIClient chatGptClient; - @InjectMocks - private AccountCache accountCache; + @InjectMocks private AccountCache accountCache; - @Test - public void sayHelloToGPT() throws Exception { - ChangeSetData changeSetData = new ChangeSetData(1, config.getVotingMinScore(), config.getMaxReviewFileSize()); - AIChatPromptStateless AIChatPromptStateless = new AIChatPromptStateless(config, true); - when(config.getAIDomain()).thenReturn(Configuration.OPENAI_DOMAIN); - when(config.getAIToken()).thenReturn("Your GPT token"); - when(config.getAIModel()).thenReturn(Configuration.DEFAULT_CHATGPT_MODEL); - when(AIChatPromptStateless.getAISystemPrompt()).thenReturn(AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT); + @Test + public void sayHelloToGPT() throws Exception { + ChangeSetData changeSetData = + new ChangeSetData(1, config.getVotingMinScore(), config.getMaxReviewFileSize()); + AIChatPromptStateless AIChatPromptStateless = new AIChatPromptStateless(config, true); + when(config.getAIDomain()).thenReturn(Configuration.OPENAI_DOMAIN); + when(config.getAIToken()).thenReturn("Your GPT token"); + when(config.getAIModel()).thenReturn(Configuration.DEFAULT_CHATGPT_MODEL); + when(AIChatPromptStateless.getAISystemPrompt()) + .thenReturn(AIChatPromptStateless.DEFAULT_AI_CHAT_SYSTEM_PROMPT); - AIChatResponseContent answer = chatGptClient.ask(changeSetData, new GerritChange(""), "hello"); - log.info("answer: {}", answer); - assertNotNull(answer); - } + AIChatResponseContent answer = chatGptClient.ask(changeSetData, new GerritChange(""), "hello"); + log.info("answer: {}", answer); + assertNotNull(answer); + } - @Test - public void getPatchSet() throws Exception { - when(config.getGerritUserName()).thenReturn("Your Gerrit username"); + @Test + public void getPatchSet() throws Exception { + when(config.getGerritUserName()).thenReturn("Your Gerrit username"); - String patchSet = gerritClient.getPatchSet("${changeId}"); - log.info("patchSet: {}", patchSet); - assertNotNull(patchSet); - } + String patchSet = gerritClient.getPatchSet("${changeId}"); + log.info("patchSet: {}", patchSet); + assertNotNull(patchSet); + } - @Test - public void setReview() throws Exception { - ChangeSetData changeSetData = new ChangeSetData(1, config.getVotingMinScore(), config.getMaxReviewFileSize()); - Localizer localizer = new Localizer(config); - when(config.getGerritUserName()).thenReturn("Your Gerrit username"); + @Test + public void setReview() throws Exception { + ChangeSetData changeSetData = + new ChangeSetData(1, config.getVotingMinScore(), config.getMaxReviewFileSize()); + Localizer localizer = new Localizer(config); + when(config.getGerritUserName()).thenReturn("Your Gerrit username"); - List<ReviewBatch> reviewBatches = new ArrayList<>(); - reviewBatches.add(new ReviewBatch("message")); + List<ReviewBatch> reviewBatches = new ArrayList<>(); + reviewBatches.add(new ReviewBatch("message")); - GerritClientReview gerritClientReview = new GerritClientReview(config, accountCache, pluginDataHandlerProvider, localizer); - gerritClientReview.setReview(new GerritChange("Your changeId"), reviewBatches, changeSetData); - } + GerritClientReview gerritClientReview = + new GerritClientReview(config, accountCache, pluginDataHandlerProvider, localizer); + gerritClientReview.setReview(new GerritChange("Your changeId"), reviewBatches, changeSetData); + } }