Merge changes I2c8adb06,I43613aa6,I3dde13c0
* changes:
Use jgit @NonNull annotation
Add license headers
Format source code using google-java-format 1.24.0
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..39c937d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/Module.java
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview;
import com.google.gerrit.server.events.EventListener;
@@ -6,9 +20,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..4d4f41f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/aicodereview/PatchSetReviewer.java
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview;
import com.google.inject.Inject;
@@ -11,166 +25,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..0185c47 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,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +30,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..40a9a79 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.config;
import com.google.common.base.Strings;
@@ -7,412 +21,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;
-
+import org.eclipse.jgit.annotations.NonNull;
@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);
+ @NonNull
+ 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..e7ca9f3 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
@@ -1,48 +1,62 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.config;
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..514d3ae 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.data;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -8,43 +22,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..f0ac342 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.data;
import com.google.gerrit.server.account.AccountCache;
@@ -7,17 +21,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..798d79e 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,24 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +26,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..594d189 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
@@ -1,29 +1,43 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.data;
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..0ea85f9 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,59 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..42cb319 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,27 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..8044bd2 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
@@ -1,17 +1,36 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.gerrit;
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..244b3ef 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.common.client.api.openapi;
import com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit.GerritChange;
@@ -5,7 +19,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..bd055b1 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,27 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..9d340c6 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
@@ -1,12 +1,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.interfaces.mode.stateful.client.prompt;
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..8efe7ed 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.listener;
import com.google.gerrit.extensions.annotations.PluginName;
@@ -5,36 +19,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..0c28cf2 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.listener;
import com.google.common.annotations.VisibleForTesting;
@@ -15,154 +29,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..bf70a6b 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.listener;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -11,43 +25,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..439b9a0 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.listener;
import com.googlesource.gerrit.plugins.aicodereview.PatchSetReviewer;
@@ -9,41 +23,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..9c8f2f9 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,21 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +24,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..9eebf35 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,21 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +24,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..5e03787 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,72 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..f599b21 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
@@ -1,21 +1,35 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.localization;
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..4e6860e 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
@@ -1,11 +1,25 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client;
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..f6355d9 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit;
import com.google.gerrit.entities.BranchNameKey;
@@ -6,68 +20,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..dfc75e9 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit;
import com.google.inject.Inject;
@@ -5,61 +19,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..8933309 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,101 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..21e9abb 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
@@ -1,20 +1,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit;
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..18a6503 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,25 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +34,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..0366efc 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,22 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +29,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 +37,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..bee18aa 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.gerrit;
import com.google.common.annotations.VisibleForTesting;
@@ -8,73 +22,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..ad091c4 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,22 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +29,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..bb09bdf 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,29 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +34,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..9a62d6c 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,121 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..7ee587d 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
@@ -1,38 +1,52 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.api.openai;
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..ffc3f07 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,47 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..a37a733 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.commands;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -7,190 +21,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..06d1588 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,91 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 java.io.IOException;
-import java.util.Map;
-
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 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..9bed490 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,79 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..47d3dcb 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -6,103 +20,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..fba17cf 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,53 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..a234b9f 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,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.messages;
-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"));
- }
+import com.googlesource.gerrit.plugins.aicodereview.localization.Localizer;
+import java.util.List;
+import java.util.Map;
- public String getDebugCodeBlock(Map<String, String> dynamicConfig) {
- return super.getDebugCodeBlock(List.of(
- prettyStringifyMap(dynamicConfig)
- ));
- }
+public class DebugCodeBlocksDynamicSettings extends DebugCodeBlocks {
+ 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)));
+ }
}
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..1306837 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,34 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..6300ae9 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,176 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..74d948a 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,100 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.code;
-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 java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
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.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 java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+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..837261a 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
@@ -1,44 +1,60 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.patch.comment;
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.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..b999b02 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,155 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..ccf9747 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -10,29 +24,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..f1b2624 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,21 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +23,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..2473bb5 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -5,67 +19,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..1bf8b8f 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,67 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..acaa7e5 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -7,36 +21,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..b0f83ab 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,184 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..47e0ab6 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
@@ -1,148 +1,174 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
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..31a9d16 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -17,38 +31,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..5330e41 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
@@ -1,22 +1,36 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.client.prompt;
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..64c5d00 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,73 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..b7f5de3 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit;
import com.google.gson.annotations.SerializedName;
@@ -7,12 +21,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..6b3fb93 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit;
import com.google.gson.annotations.SerializedName;
@@ -6,52 +20,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..148b142 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit;
import com.google.gson.annotations.SerializedName;
@@ -5,15 +19,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..5d56599 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,53 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..88f9e91 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,32 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..e266647 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
@@ -1,9 +1,23 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.gerrit;
import lombok.Data;
@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..4ffc969 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,35 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..1425bf2 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,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..0a299c4 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
@@ -1,11 +1,25 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai;
import lombok.Data;
@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..259bc7a 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,26 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..98fc9de 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai;
import lombok.Data;
@@ -6,9 +20,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..614dc50 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai;
import lombok.Builder;
@@ -6,8 +20,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..b2df1d5 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,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..2cbefb0 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,37 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..e9c915e 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,33 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..98266c6 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,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..9611417 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,80 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..2abe200 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
@@ -1,16 +1,30 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai;
import lombok.Data;
@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..9d49dd8 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
@@ -1,14 +1,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.api.openai;
import lombok.Data;
@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..195bb58 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,50 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..ce64132 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,29 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..c73da04 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
@@ -1,26 +1,39 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.data;
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..71f9edf 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,27 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..dbb39a7 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
@@ -1,10 +1,24 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.common.model.patch.diff;
import lombok.Data;
@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..e907ed6 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,36 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..cfefee3 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,63 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..5ef8994 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,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +35,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..4954814 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,172 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..a377f98 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,64 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..f4d3c19 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
@@ -1,15 +1,30 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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.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..c93d0bc 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,22 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +26,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..0c310e3 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,64 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..1be88af 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,22 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +26,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..313132f 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,67 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..4f8f948 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,64 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 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 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 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..9cd1d3a 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,82 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..e746f36 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,100 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..75be8d6 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -7,13 +21,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..13edb30 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,81 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..42aecb3 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
@@ -1,35 +1,50 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.client.prompt;
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 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..105aa37 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,72 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..9d52912 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
@@ -8,12 +22,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..c5e769e 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
@@ -7,6 +21,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..9051559 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
@@ -7,7 +21,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..bc73af0 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import lombok.Data;
@@ -6,5 +20,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..9b719ef 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,24 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..72af0be 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
@@ -1,10 +1,24 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import lombok.Data;
@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..9a60325 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
@@ -8,6 +22,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..d96d35a 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,36 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..f341de0 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateful.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
@@ -7,13 +21,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..5d022b5 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,62 @@
-package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.api;
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+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..3b782a4 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,141 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..dc7ad66 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,95 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..9166d74 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.client.prompt;
import com.googlesource.gerrit.plugins.aicodereview.config.Configuration;
@@ -10,20 +24,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..692d3a9 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,122 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..85d9abf 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
@@ -1,23 +1,37 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.mode.stateless.model.api.chatgpt;
import com.google.gson.annotations.SerializedName;
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..8c1ecfa 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
@@ -1,36 +1,52 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.settings;
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..d236f79 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.utils;
import java.io.IOException;
@@ -9,31 +23,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..9b7e8eb 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
@@ -1,16 +1,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.utils;
import com.google.gson.Gson;
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..20e168c 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.utils;
import java.nio.charset.StandardCharsets;
@@ -7,33 +21,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..415ea4c 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,34 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..4e8fe43 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,41 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..7a65657 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,122 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..f549d38 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,26 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..ac0be6d 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
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview.utils;
import java.time.LocalDateTime;
@@ -5,9 +19,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..51b209d 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,28 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +33,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 +52,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..6202b2e 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,29 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +31,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 +50,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..469f218 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,29 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +34,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 +51,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 +65,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 +73,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..a1ed09c 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,52 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..d850736 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,27 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +30,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..b44a0c6 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,48 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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..917c985 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/aicodereview/TestGerritEventContextModule.java
@@ -1,3 +1,17 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.googlesource.gerrit.plugins.aicodereview;
import com.google.gerrit.extensions.annotations.PluginData;
@@ -5,19 +19,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..962eb61 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,22 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
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 +29,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 +39,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);
+ }
}