@@ -42,10 +42,18 @@ import kotlinx.coroutines.channels.Channel
4242import kotlinx.coroutines.flow.receiveAsFlow
4343import kotlinx.coroutines.future.await
4444import kotlinx.coroutines.launch
45+ import kotlinx.coroutines.withContext
4546import migration.software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
47+ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
4648import org.eclipse.lsp4j.jsonrpc.messages.Either
49+ import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException
4750import software.aws.toolkits.core.utils.debug
4851import software.aws.toolkits.core.utils.getLogger
52+ import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineBgContext
53+ import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
54+ import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
55+ import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
56+ import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
4957import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
5058import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
5159import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdder
@@ -59,8 +67,10 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe
5967import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
6068import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
6169import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
70+ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil
6271import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics
6372import software.aws.toolkits.jetbrains.utils.isQConnected
73+ import software.aws.toolkits.jetbrains.utils.isQExpired
6474import software.aws.toolkits.resources.message
6575import software.aws.toolkits.telemetry.CodewhispererTriggerType
6676import java.awt.Dimension
@@ -393,8 +403,22 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
393403
394404 override suspend fun getSuggestion (request : InlineCompletionRequest ): InlineCompletionSuggestion {
395405 val editor = request.editor
396- val document = editor.document
397406 val project = editor.project ? : return InlineCompletionSuggestion .Empty
407+
408+ // try to refresh automatically if possible, otherwise ask user to login again
409+ if (isQExpired(project)) {
410+ // consider changing to only running once a ~minute since this is relatively expensive
411+ // say the connection is un-refreshable if refresh fails for 3 times
412+ val shouldReauth = withContext(getCoroutineBgContext()) {
413+ CodeWhispererUtil .promptReAuth(project)
414+ }
415+
416+ if (shouldReauth) {
417+ return InlineCompletionSuggestion .Empty
418+ }
419+ }
420+
421+ val document = editor.document
398422 val handler = InlineCompletion .getHandlerOrNull(editor) ? : return InlineCompletionSuggestion .Empty
399423 val session = InlineCompletionSession .getOrNull(editor) ? : return InlineCompletionSuggestion .Empty
400424 val triggerSessionId = triggerSessionId++
@@ -446,31 +470,28 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
446470 }
447471
448472 try {
449- // Launch coroutine for background pagination progress
450- cs.launch {
451- var nextToken: Either <String , Int >? = null
452- do {
453- nextToken = startPaginationInBackground(
454- project,
455- editor,
456- triggerTypeInfo,
457- triggerSessionId,
458- nextToken,
459- sessionContext,
460- )
461- } while (nextToken != null && ! nextToken.left.isNullOrEmpty())
473+ var nextToken: Either <String , Int >? = null
474+ do {
475+ nextToken = startPaginationInBackground(
476+ project,
477+ editor,
478+ triggerTypeInfo,
479+ triggerSessionId,
480+ nextToken,
481+ sessionContext,
482+ )
483+ } while (nextToken != null && ! nextToken.left.isNullOrEmpty())
462484
463- // closing all channels since pagination for this session has finished
485+ // closing all channels since pagination for this session has finished
486+ logInline(triggerSessionId) {
487+ " Pagination finished, closing all channels"
488+ }
489+ sessionContext.itemContexts.forEach {
490+ it.channel.close()
491+ }
492+ if (session.context.isDisposed) {
464493 logInline(triggerSessionId) {
465- " Pagination finished, closing all channels"
466- }
467- sessionContext.itemContexts.forEach {
468- it.channel.close()
469- }
470- if (session.context.isDisposed) {
471- logInline(triggerSessionId) {
472- " Current display session already disposed by a new trigger before pagination finishes, exiting"
473- }
494+ " Current display session already disposed by a new trigger before pagination finishes, exiting"
474495 }
475496 }
476497
@@ -575,6 +596,27 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
575596 logInline(triggerSessionId, e) {
576597 " Error during pagination"
577598 }
599+ if (e is ResponseErrorException ) {
600+ // convoluted but lines up with "The bearer token included in the request is invalid"
601+ // https://github.com/aws/language-servers/blob/1f3e93024eeb22186a34f0bd560f8d552f517300/server/aws-lsp-codewhisperer/src/language-server/chat/utils.ts#L22-L23
602+ // error data is nullable
603+ if (e.responseError.data?.toString()?.contains(" E_AMAZON_Q_CONNECTION_EXPIRED" ) == true ) {
604+ // kill the session if the connection is expired
605+ val connection = ToolkitConnectionManager
606+ .getInstance(project)
607+ .activeConnectionForFeature(QConnection .getInstance()) as ? AwsBearerTokenConnection
608+ val tokenProvider = connection?.let { it.getConnectionSettings().tokenProvider.delegate as ? BearerTokenProvider }
609+ tokenProvider?.let {
610+ // TODO: fragile
611+ try {
612+ it.refresh()
613+ } catch (_: InvalidGrantException ) {
614+ it.invalidate()
615+ CodeWhispererUtil .reconnectCodeWhisperer(project)
616+ }
617+ }
618+ }
619+ }
578620 return null
579621 }
580622 }
@@ -594,6 +636,7 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
594636 val editor = request.editor
595637 val project = editor.project ? : return false
596638
639+ // qExpired case handled in completion handler
597640 if (! isQConnected(project)) return false
598641 if (QRegionProfileManager .getInstance().hasValidConnectionButNoActiveProfile(project)) return false
599642 if (event.isManualCall()) return true
0 commit comments