From faa503eb3dc0716cb2064a1714587f7637a04c01 Mon Sep 17 00:00:00 2001 From: Shruti Sinha <44882001+shruti0085@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:56:05 -0800 Subject: [PATCH] Determine and send inline completion session result (#323) --- plugin/META-INF/MANIFEST.MF | 3 +- .../handlers/QAcceptSuggestionsHandler.java | 2 + .../eclipse/amazonq/lsp/AmazonQLspServer.java | 4 + .../lsp/model/InlineCompletionStates.java | 43 ++++ ...gInlineCompletionSessionResultsParams.java | 63 ++++++ .../amazonq/util/QInlineInputListener.java | 26 ++- .../amazonq/util/QInvocationSession.java | 207 +++++++++++++++--- .../amazonq/util/QSuggestionsContext.java | 23 ++ .../amazonq/util/QInvocationSessionTest.java | 1 + 9 files changed, 344 insertions(+), 28 deletions(-) create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionStates.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/LogInlineCompletionSessionResultsParams.java diff --git a/plugin/META-INF/MANIFEST.MF b/plugin/META-INF/MANIFEST.MF index 1e50ce3a..f00b620d 100644 --- a/plugin/META-INF/MANIFEST.MF +++ b/plugin/META-INF/MANIFEST.MF @@ -25,7 +25,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.31.0", org.eclipse.jetty.util;bundle-version="12.0.9", org.eclipse.core.net;bundle-version="1.5.400", org.apache.commons.logging;bundle-version="1.2.0", - slf4j.api;bundle-version="2.0.13" + slf4j.api;bundle-version="2.0.13", + org.apache.commons.lang3;bundle-version="3.14.0" Bundle-Classpath: target/classes/, target/dependency/annotations-2.28.26.jar, target/dependency/apache-client-2.28.26.jar, diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/handlers/QAcceptSuggestionsHandler.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/handlers/QAcceptSuggestionsHandler.java index 16508725..0f83c610 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/handlers/QAcceptSuggestionsHandler.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/handlers/QAcceptSuggestionsHandler.java @@ -25,6 +25,8 @@ public final synchronized Object execute(final ExecutionEvent event) throws Exec var suggestion = QInvocationSession.getInstance().getCurrentSuggestion(); var widget = QInvocationSession.getInstance().getViewer().getTextWidget(); var session = QInvocationSession.getInstance(); + // mark current suggestion as accepted + session.setAccepted(suggestion.getItemId()); session.setSuggestionAccepted(true); session.transitionToDecisionMade(); Display display = widget.getDisplay(); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java index 7f6d6e52..0c38ce00 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java @@ -22,6 +22,7 @@ import software.aws.toolkits.eclipse.amazonq.lsp.model.GetConfigurationFromServerParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionResponse; +import software.aws.toolkits.eclipse.amazonq.lsp.model.LogInlineCompletionSessionResultsParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.LspServerConfigurations; import software.aws.toolkits.eclipse.amazonq.lsp.model.UpdateCredentialsPayload; @@ -30,6 +31,9 @@ public interface AmazonQLspServer extends LanguageServer { @JsonRequest("aws/textDocument/inlineCompletionWithReferences") CompletableFuture inlineCompletionWithReferences(InlineCompletionParams params); + @JsonNotification("aws/logInlineCompletionSessionResults") + void logInlineCompletionSessionResult(LogInlineCompletionSessionResultsParams params); + @JsonRequest("aws/chat/sendChatPrompt") CompletableFuture sendChatPrompt(EncryptedChatParams encryptedChatRequestParams); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionStates.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionStates.java new file mode 100644 index 00000000..7ba8e3a5 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/InlineCompletionStates.java @@ -0,0 +1,43 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +public final class InlineCompletionStates { + // Indicates if suggestion has been seen by the user in the UI + private boolean seen; + // Indicates if suggestion accepted + private boolean accepted; + // Indicates if suggestion was filtered out on the client-side and marked as + // discarded. + private boolean discarded; + + public boolean isSeen() { + return seen; + } + + public void setSeen(final boolean seen) { + this.seen = seen; + } + + public boolean isAccepted() { + return accepted; + } + + public void setAccepted(final boolean accepted) { + this.accepted = accepted; + } + + public boolean isDiscarded() { + return discarded; + } + + public void setDiscarded(final boolean discarded) { + this.discarded = discarded; + } + + @Override + public String toString() { + return String.format("{accepted=%b, seen=%b, discarded=%b}", accepted, seen, discarded); + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/LogInlineCompletionSessionResultsParams.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/LogInlineCompletionSessionResultsParams.java new file mode 100644 index 00000000..6eb24b88 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/model/LogInlineCompletionSessionResultsParams.java @@ -0,0 +1,63 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.eclipse.amazonq.lsp.model; + +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonInclude; + +public final class LogInlineCompletionSessionResultsParams { + // Session Id attached to get completion items response + private final String sessionId; + // Map with results of interaction with completion items/suggestions in the UI + private final ConcurrentHashMap completionSessionResult; + + // Total time when items from this suggestion session were visible in UI + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long totalSessionDisplayTime; + // Time from request invocation start to rendering of the first suggestion in the UI. + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long firstCompletionDisplayLatency; + // Length of additional characters inputed by user from when the trigger happens to when the first suggestion is about to be shown in UI + @JsonInclude(JsonInclude.Include.NON_NULL) + private Integer typeaheadLength; + + public LogInlineCompletionSessionResultsParams(final String sessionId, final ConcurrentHashMap completionSessionResult) { + this.sessionId = sessionId; + this.completionSessionResult = completionSessionResult; + } + + public Long getTotalSessionDisplayTime() { + return totalSessionDisplayTime; + } + + public void setTotalSessionDisplayTime(final Long totalSessionDisplayTime) { + this.totalSessionDisplayTime = totalSessionDisplayTime; + } + + public Long getFirstCompletionDisplayLatency() { + return firstCompletionDisplayLatency; + } + + public void setFirstCompletionDisplayLatency(final Long firstCompletionDisplayLatency) { + this.firstCompletionDisplayLatency = firstCompletionDisplayLatency; + } + + public Integer getTypeaheadLength() { + return typeaheadLength; + } + + public void setTypeaheadLength(final Integer typeaheadLength) { + this.typeaheadLength = typeaheadLength; + } + + public ConcurrentHashMap getCompletionSessionResult() { + return completionSessionResult; + } + + public String getSessionId() { + return sessionId; + } + +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineInputListener.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineInputListener.java index 918127eb..36651d3e 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineInputListener.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInlineInputListener.java @@ -274,8 +274,11 @@ public void documentChanged(final DocumentEvent event) { } String currentSuggestion = session.getCurrentSuggestion().getInsertText(); int currentOffset = widget.getCaretOffset(); + if (input.isEmpty()) { if (distanceTraversed <= 0) { + // discard all suggestions as caret position is less than request invocation position + session.updateCompletionStates(new ArrayList()); session.transitionToDecisionMade(); session.end(); return; @@ -283,9 +286,21 @@ public void documentChanged(final DocumentEvent event) { distanceTraversed = typeaheadProcessor.getNewDistanceTraversedOnDeleteAndUpdateBracketState( event.getLength(), distanceTraversed, brackets); if (distanceTraversed < 0) { + // discard all suggestions as caret position is less than request invocation position + session.updateCompletionStates(new ArrayList()); session.transitionToDecisionMade(); session.end(); } + + // note: distanceTraversed as 0 is currently understood to be when a user presses BS removing any typeahead + if (distanceTraversed == 0) { + // reset completion states for all suggestions when user reverts to request + // invocation state + session.resetCompletionStates(); + // mark currently displayed suggestion as seen + session.markSuggestionAsSeen(); + } + return; } @@ -328,12 +343,19 @@ public void documentChanged(final DocumentEvent event) { session.transitionToDecisionMade(lineToUnsetIndent); Display.getCurrent().asyncExec(() -> { if (session.isActive()) { - session.end(); + // discard suggestions and end immediately as typeahead does not match + session.updateCompletionStates(new ArrayList()); + session.endImmediately(); } }); return; } + // discard all other suggestions except for current one as typeahead matches it + // also mark current one as seen as it continues to be displayed + session.updateCompletionStates(List.of(session.getCurrentSuggestion().getItemId())); + session.markSuggestionAsSeen(); + // Here we perform "post closing bracket insertion caret correction", which // consists of the following: // - Check if the input is a closing bracket @@ -398,7 +420,7 @@ public void mouseDown(final MouseEvent e) { int currentOffset = invocationOffset + distanceTraversed; int lastKnownLine = widget.getLineAtOffset(currentOffset); qInvocationSessionInstance.transitionToDecisionMade(lastKnownLine + 1); - qInvocationSessionInstance.end(); + qInvocationSessionInstance.endImmediately(); return; } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java index 0bfa94a8..029ae038 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QInvocationSession.java @@ -3,6 +3,8 @@ package software.aws.toolkits.eclipse.amazonq.util; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.ITextViewer; import org.eclipse.swt.custom.CaretListener; @@ -12,15 +14,21 @@ import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionItem; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionParams; +import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionStates; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionTriggerKind; +import software.aws.toolkits.eclipse.amazonq.lsp.model.LogInlineCompletionSessionResultsParams; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; import software.aws.toolkits.eclipse.amazonq.views.model.InlineSuggestionCodeReference; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.function.Consumer; @@ -39,6 +47,7 @@ public final class QInvocationSession extends QResource { private boolean isMacOS; private QSuggestionsContext suggestionsContext = null; + private ConcurrentHashMap suggestionCompletionResults = new ConcurrentHashMap(); private ITextEditor editor = null; private ITextViewer viewer = null; @@ -57,6 +66,10 @@ public final class QInvocationSession extends QResource { private Runnable changeStatusToQuerying; private Runnable changeStatusToIdle; private Runnable changeStatusToPreviewing; + private boolean hasSeenFirstSuggestion = false; + private long firstSuggestionDisplayLatency; + private StopWatch suggestionDisplaySessionStopWatch = new StopWatch(); + private Optional initialTypeaheadLength = Optional.empty(); // Private constructor to prevent instantiation private QInvocationSession() { @@ -152,32 +165,37 @@ public void invoke() { private synchronized void queryAsync(final InlineCompletionParams params, final int invocationOffset) { var uuid = UUID.randomUUID(); - long invocationTimeInMs = System.currentTimeMillis(); Activator.getLogger().info(uuid + " queried made at " + invocationOffset); var future = ThreadingUtils.executeAsyncTaskAndReturnFuture(() -> { try { var session = QInvocationSession.getInstance(); - - List newSuggestions = Activator.getLspProvider().getAmazonQServer().get() - .inlineCompletionWithReferences(params) - .thenApply(result -> result.getItems().parallelStream().map(item -> { - if (isTabOnly) { - String sanitizedText = replaceSpacesWithTabs(item.getInsertText(), tabSize); - item.setInsertText(sanitizedText); - } - return item; - }).collect(Collectors.toList())).get(); - unresolvedTasks.remove(uuid); + List newSuggestions = new ArrayList(); + List sessionId = new ArrayList(); + long requestInvocation = System.currentTimeMillis(); + + // request lsp for suggestions + var response = Activator.getLspProvider().getAmazonQServer().get() + .inlineCompletionWithReferences(params); + response.thenAccept(result -> { + sessionId.add(result.getSessionId()); + var suggestions = result.getItems().parallelStream().map(item -> { + if (isTabOnly) { + String sanitizedText = replaceSpacesWithTabs(item.getInsertText(), tabSize); + item.setInsertText(sanitizedText); + } + return item; + }).collect(Collectors.toList()); + newSuggestions.addAll(suggestions); + }).get(); Display.getDefault().asyncExec(() -> { - long curTimeInMs = System.currentTimeMillis(); - long timeUsedInMs = curTimeInMs - invocationTimeInMs; - if (newSuggestions == null || newSuggestions.isEmpty()) { + unresolvedTasks.remove(uuid); + + if (newSuggestions == null || newSuggestions.isEmpty() || sessionId.get(0) == null || sessionId.get(0).isEmpty()) { if (!session.isPreviewingSuggestions()) { end(); } - Activator.getLogger() - .info(uuid + " returned with no result. Time used: " + timeUsedInMs + " ms"); + Activator.getLogger().info(uuid + " returned with no result."); if (params.getContext().getTriggerKind() == InlineCompletionTriggerKind.Invoke) { Display display = Display.getDefault(); String message = "Q returned no suggestions"; @@ -185,10 +203,16 @@ private synchronized void queryAsync(final InlineCompletionParams params, final } return; } else { - Activator.getLogger().info(uuid + " returned with " + newSuggestions.size() - + " results. Time used: " + timeUsedInMs + " ms"); + Activator.getLogger().info(uuid + " returned with " + newSuggestions.size() + " results."); } + suggestionsContext.setSessionId(sessionId.get(0)); + suggestionsContext.setRequestedAtEpoch(requestInvocation); + suggestionsContext.getDetails() + .addAll(newSuggestions.stream().map(QSuggestionContext::new).collect(Collectors.toList())); + + initializeSuggestionCompletionResults(); + // If the caret positions has moved on from the invocation offset, we need to // see if there exists in the suggestions fetched // one more suggestions that qualify for what has been typed since the @@ -199,6 +223,8 @@ private synchronized void queryAsync(final InlineCompletionParams params, final boolean hasAMatch = false; var viewer = session.getViewer(); if (viewer == null || viewer.getTextWidget() == null || viewer.getTextWidget().getCaretOffset() < invocationOffset) { + // discard all suggestions since the current caret is behind request position + updateCompletionStates(new ArrayList()); end(); return; } @@ -206,25 +232,37 @@ private synchronized void queryAsync(final InlineCompletionParams params, final if (viewer != null && viewer.getTextWidget() != null && viewer.getTextWidget().getCaretOffset() > invocationOffset) { var widget = viewer.getTextWidget(); int currentOffset = widget.getCaretOffset(); + String prefix = widget.getTextRange(invocationOffset, currentOffset - invocationOffset); + // Computes the typed prefix and typeahead length from when user invocation happened to + // before suggestions are first shown in UI + // Note: This computation may change later on but follows the same pattern for consistency across IDEs for now + session.initialTypeaheadLength = Optional.of(prefix.length()); + for (int i = 0; i < newSuggestions.size(); i++) { - String prefix = widget.getTextRange(invocationOffset, currentOffset - invocationOffset); if (newSuggestions.get(i).getInsertText().startsWith(prefix)) { currentIdxInSuggestion = i; hasAMatch = true; break; } } + // indicates that typeahead prefix does not match any suggestions if (invocationOffset != currentOffset && !hasAMatch) { + // all suggestions filtered out, mark them as discarded + updateCompletionStates(new ArrayList()); end(); return; } + + // if typeahead exists, mark all suggestions except for current suggestion index with match as discarded + // As of Jan 25, current logic blocks users from toggling between suggestions when a typeahead exists in QToggleSuggestionsHandler + if (invocationOffset != currentOffset && hasAMatch) { + var currentSuggestion = suggestionsContext.getDetails().get(currentIdxInSuggestion); + var filteredSuggestions = List.of(currentSuggestion.getInlineCompletionItem().getItemId()); + updateCompletionStates(filteredSuggestions); + } } session.invocationOffset = invocationOffset; - - suggestionsContext.getDetails() - .addAll(newSuggestions.stream().map(QSuggestionContext::new).collect(Collectors.toList())); - suggestionsContext.setCurrentIndex(currentIdxInSuggestion); session.transitionToPreviewingState(); @@ -241,6 +279,64 @@ private synchronized void queryAsync(final InlineCompletionParams params, final unresolvedTasks.put(uuid, future); } + + /* + * Updates completion state of each suggestion in the `suggestionCompletionResult` map, given the updated filtered suggestion list + * @param filteredSuggestions + */ + public void updateCompletionStates(final List filteredSuggestions) { + // Mark all suggestions as unseen and discarded + suggestionCompletionResults.values().forEach(item -> { + item.setSeen(false); + item.setDiscarded(true); + }); + + // Reset discarded state for filtered suggestions + filteredSuggestions.forEach(itemId -> { + var item = suggestionCompletionResults.get(itemId); + item.setDiscarded(false); + }); + } + + /* + * Mark all suggestions as unseen and not discarded + */ + public void resetCompletionStates() { + suggestionCompletionResults.values().forEach(item -> { + item.setSeen(false); + item.setDiscarded(false); + }); + } + + private void initializeSuggestionCompletionResults() { + suggestionsContext.getDetails().forEach(suggestion -> suggestionCompletionResults + .put(suggestion.getInlineCompletionItem().getItemId(), new InlineCompletionStates())); + } + + public void setAccepted(final String suggestionId) { + var item = Optional.ofNullable(suggestionCompletionResults.get(suggestionId)); + item.ifPresent(result -> result.setAccepted(true)); + } + + /* + * When a suggestion is seen, marks it and starts display timer + */ + public void markSuggestionAsSeen() { + var index = suggestionsContext.getCurrentIndex(); + var suggestion = suggestionsContext.getDetails().get(index); + var item = Optional + .ofNullable(suggestionCompletionResults.get(suggestion.getInlineCompletionItem().getItemId())); + item.ifPresent(result -> { + result.setSeen(true); + // if this was the first suggestion displayed, start suggestion session display timer and record first suggestion's display latency + if (!hasSeenFirstSuggestion) { + suggestionDisplaySessionStopWatch.start(); + firstSuggestionDisplayLatency = System.currentTimeMillis() - suggestionsContext.getRequestedAtEpoch(); + hasSeenFirstSuggestion = true; + } + }); + } + // Method to end the session public void end() { if (isActive() && unresolvedTasks.isEmpty()) { @@ -257,6 +353,10 @@ public void end() { } } + + /* + * Usually called when user attempts to cancel the suggestion session such as by a diverging typeahead or hitting escape + */ public void endImmediately() { if (isActive()) { if (state == QInvocationSessionState.SUGGESTION_PREVIEWING) { @@ -455,6 +555,7 @@ public int getOutstandingPadding() { public void primeListeners() { inputListener.onNewSuggestion(); paintListener.onNewSuggestion(); + markSuggestionAsSeen(); } public int getLastKnownLine() { @@ -495,12 +596,68 @@ public boolean isMacOS() { return isMacOS; } + /* + * Sends inline completion results for current suggestion session over to lsp for use with telemetry + */ + private void sendSessionCompletionResult() { + try { + if (StringUtils.isEmpty(suggestionsContext.getSessionId()) || suggestionsContext.getDetails() == null + || suggestionsContext.getDetails().isEmpty()) { + return; + } + if (suggestionDisplaySessionStopWatch.isStarted()) { + suggestionDisplaySessionStopWatch.stop(); + } + + final ConcurrentHashMap completionStatesCopy = new ConcurrentHashMap(); + // Explicitly copy the map contents + for (Map.Entry entry : suggestionCompletionResults.entrySet()) { + completionStatesCopy.put(String.valueOf(entry.getKey()), entry.getValue()); + } + + var result = new LogInlineCompletionSessionResultsParams(suggestionsContext.getSessionId(), + completionStatesCopy); + if (firstSuggestionDisplayLatency > 0L) { + result.setFirstCompletionDisplayLatency(firstSuggestionDisplayLatency); + } + + var sessionTime = suggestionDisplaySessionStopWatch.getTime(TimeUnit.MILLISECONDS); + if (sessionTime > 0L) { + result.setTotalSessionDisplayTime(sessionTime); + } + + if (initialTypeaheadLength.isPresent()) { + result.setTypeaheadLength(initialTypeaheadLength.get()); + } + sendCompletionSessionResult(result); + } catch (Exception e) { + Activator.getLogger() + .error("Error occurred when sending suggestion completion results to Amazon Q language server", e); + } + } + + private void sendCompletionSessionResult(final LogInlineCompletionSessionResultsParams result) + throws InterruptedException, ExecutionException { + ThreadingUtils.executeAsyncTask(() -> { + Activator.getLspProvider().getAmazonQServer().thenAccept(lsp -> lsp.logInlineCompletionSessionResult(result)); + }); + } + + private void resetSessionResultParams() { + hasSeenFirstSuggestion = false; + firstSuggestionDisplayLatency = 0L; + suggestionDisplaySessionStopWatch.reset(); + suggestionCompletionResults.clear(); + initialTypeaheadLength = Optional.empty(); + } + // Additional methods for the session can be added here @Override public void dispose() { var widget = viewer.getTextWidget(); - + sendSessionCompletionResult(); suggestionsContext = null; + resetSessionResultParams(); if (inlineTextFont != null) { inlineTextFont.dispose(); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionsContext.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionsContext.java index 751f6cf6..41aa5f27 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionsContext.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/QSuggestionsContext.java @@ -4,10 +4,17 @@ package software.aws.toolkits.eclipse.amazonq.util; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionStates; + public class QSuggestionsContext { private List details = new ArrayList<>(); + private String sessionId; + private HashMap suggestionCompletionResults = new HashMap(); + + private long requestedAtEpoch; private int currentIndex = -1; public QSuggestionsContext() { @@ -40,5 +47,21 @@ public final void decrementIndex() { public final int getNumberOfSuggestions() { return details.size(); } + + public final String getSessionId() { + return sessionId; + } + + public final void setSessionId(final String sessionId) { + this.sessionId = sessionId; + } + + public final long getRequestedAtEpoch() { + return requestedAtEpoch; + } + + public final void setRequestedAtEpoch(final long requestedAtEpoch) { + this.requestedAtEpoch = requestedAtEpoch; + } } diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/QInvocationSessionTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/QInvocationSessionTest.java index 59134ca5..f01a7bd1 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/QInvocationSessionTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/util/QInvocationSessionTest.java @@ -248,6 +248,7 @@ static MockedStatic mockLspProvider() { localizedActivatorMock.when(Activator::getLspProvider).thenReturn(mockLspProvider); potentResponse = mock(InlineCompletionResponse.class); impotentResponse = mock(InlineCompletionResponse.class); + when(potentResponse.getSessionId()).thenReturn("sample-sessionId"); when(potentResponse.getItems()).thenReturn(new ArrayList<>(getInlineCompletionItems())); when(impotentResponse.getItems()).thenReturn(Collections.emptyList()); LoggingService loggingServiceMock = mock(LoggingService.class);