Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(amazonq): Prefetch next inline recommendation #5290

Open
wants to merge 147 commits into
base: main
Choose a base branch
from

Conversation

evanliu048
Copy link
Contributor

Types of changes

  • New feature (non-breaking change which adds functionality)

Description

This PR introduces the ability for users to see the next recommendation immediately after accepting the current one, provided there is a subsequent recommendation available. The key enhancements include:

  1. Automatic Prefetching: Introduced a function that calls the CodeWhisperer API to fetch the next recommendation while the current one is being reviewed.
  2. Session Promotion Added functionality to display the next recommendation instantly upon accepting the current recommendation (if there is one)
  3. Helper Utilities: Implemented helper functions to calculate the necessary file information for the next request and to send telemetry events related to the subsequent recommendation.

License

I confirm that my contribution is made under the terms of the Apache 2.0 license.

@evanliu048 evanliu048 requested a review from a team as a code owner January 23, 2025 00:04
@evanliu048 evanliu048 marked this pull request as draft January 23, 2025 18:05
@evanliu048 evanliu048 changed the title Prefetch next inline recommendation feat(amazonq): Prefetch next inline recommendation Jan 24, 2025
@evanliu048 evanliu048 marked this pull request as ready for review January 31, 2025 20:32
@evanliu048 evanliu048 requested a review from a team as a code owner February 1, 2025 00:47
}
val nextRecommendationContext = RecommendationContext(detailContexts, "", "", newVisualPosition)
val newPopup = withContext(EDT) {
JBPopupFactory.getInstance().createMessage("Dummy popup")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever shown to the user? Should this be a more user-friendly message?

Copy link
Contributor Author

@evanliu048 evanliu048 Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dummy popup is not shown to the user. I created it as a placeholder since it's required for the nextInvocationContext variable. If there's a need to display a popup in the future, a new user-friendly popup will be created accordingly.
https://github.com/evanliu048/aws-toolkit-jetbrains/blob/prefechNextInlineRecommendation/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt#L745-L749

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just handle the case where there is no prior popup there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prefetch process requires creating a nextInvocationContext for the service, where the popup field cannot be null. To meet this requirement, I created a dummy popup. This popup is never actually displayed but serves as a placeholder to satisfy the non-null constraint.

If the creation of this dummy popup seems unusual, an alternative would be to make the popup field nullable. However, this would require adding null checks at every location where InvocationContext.popup is accessed, which would result in many changes any need to be tested thoroughly

To minimize the impact, maintain consistency and ensure this feature can be launched this week, I opted for this approach. If there are better suggestions for handling this scenario, I'm happy to discuss them!

@evanliu048 evanliu048 requested a review from manodnyab February 3, 2025 20:34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restating my request to delete this adapter if we have no need to make dataplane requests over sigv4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification! I’m not entirely familiar with this part, so I’d appreciate some additional context. Could you elaborate on why removing this adapter is necessary?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is left over tech debt that's makes the client interactions more complicated. there is also a hidden footgun that can result in NullPointerException at runtime

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out! I can discuss this further with our team to better understand the implications. Since this is outside the scope of the current feature, would it be possible to merge this PR first and address the issue in a separate follow-up? That way, I can ensure this feature is delivered while also properly handling the tech debt.

nextInvocationContext?.popup?.let { Disposer.dispose(it) }
nextInvocationContext = null

runInEdt {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're already using coroutines, we do not need to use runInEdt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing that out! I use runInEdt in this context because several operations, such as initializing the popup (initPopup) and updating its listeners, involve UI changes that must be executed on the Event Dispatch Thread (EDT) to ensure thread safety and proper behavior. In similar cases that I’ve come across, runInEdt is consistently used, so I opted to keep it here as a safeguard.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withContext(EDT) has the same behavior has runInEdt, so it does not make sense to mix executors that move actions onto other threads with coroutines

Copy link
Contributor Author

@evanliu048 evanliu048 Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification! Just to confirm—are you suggesting that I replace runInEdt with withContext(EDT), or do you mean I should simply remove runInEdt without any replacement? Want to make sure I fully understand your expectation before making the update. Thanks!

val firstValidRecommendation = currStates.recommendationContext.details
.firstOrNull {
!it.isDiscarded && it.recommendation.content().isNotEmpty()
} ?: return SupervisorJob().apply { complete() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this actually need to return a job?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function's return type is explicitly defined as Job, and there is an existing test that relies on this return value. So I return a SupervisorJob to maintain consistency with current codebase.
https://github.com/evanliu048/aws-toolkit-jetbrains/blob/prefechNextInlineRecommendation/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererServiceTest.kt#L219

}
val nextRecommendationContext = RecommendationContext(detailContexts, "", "", newVisualPosition)
val newPopup = withContext(EDT) {
JBPopupFactory.getInstance().createMessage("Dummy popup")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just handle the case where there is no prior popup there?

evanliu048 and others added 4 commits February 3, 2025 16:24
…/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTelemetryTest.kt

Co-authored-by: Richard Li <[email protected]>
…/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererTestBase.kt

Co-authored-by: Richard Li <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants