@@ -42,10 +42,18 @@ import kotlinx.coroutines.channels.Channel
42
42
import kotlinx.coroutines.flow.receiveAsFlow
43
43
import kotlinx.coroutines.future.await
44
44
import kotlinx.coroutines.launch
45
+ import kotlinx.coroutines.withContext
45
46
import migration.software.aws.toolkits.jetbrains.services.codewhisperer.explorer.CodeWhispererExplorerActionManager
47
+ import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
46
48
import org.eclipse.lsp4j.jsonrpc.messages.Either
49
+ import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException
47
50
import software.aws.toolkits.core.utils.debug
48
51
import 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
49
57
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
50
58
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
51
59
import software.aws.toolkits.jetbrains.services.codewhisperer.importadder.CodeWhispererImportAdder
@@ -59,8 +67,10 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe
59
67
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
60
68
import software.aws.toolkits.jetbrains.services.codewhisperer.toolwindow.CodeWhispererCodeReferenceManager
61
69
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
70
+ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil
62
71
import software.aws.toolkits.jetbrains.services.codewhisperer.util.getDocumentDiagnostics
63
72
import software.aws.toolkits.jetbrains.utils.isQConnected
73
+ import software.aws.toolkits.jetbrains.utils.isQExpired
64
74
import software.aws.toolkits.resources.message
65
75
import software.aws.toolkits.telemetry.CodewhispererTriggerType
66
76
import java.awt.Dimension
@@ -393,8 +403,22 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
393
403
394
404
override suspend fun getSuggestion (request : InlineCompletionRequest ): InlineCompletionSuggestion {
395
405
val editor = request.editor
396
- val document = editor.document
397
406
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
398
422
val handler = InlineCompletion .getHandlerOrNull(editor) ? : return InlineCompletionSuggestion .Empty
399
423
val session = InlineCompletionSession .getOrNull(editor) ? : return InlineCompletionSuggestion .Empty
400
424
val triggerSessionId = triggerSessionId++
@@ -446,31 +470,28 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
446
470
}
447
471
448
472
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())
462
484
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) {
464
493
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"
474
495
}
475
496
}
476
497
@@ -575,6 +596,27 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
575
596
logInline(triggerSessionId, e) {
576
597
" Error during pagination"
577
598
}
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
+ }
578
620
return null
579
621
}
580
622
}
@@ -594,6 +636,7 @@ class QInlineCompletionProvider(private val cs: CoroutineScope) : InlineCompleti
594
636
val editor = request.editor
595
637
val project = editor.project ? : return false
596
638
639
+ // qExpired case handled in completion handler
597
640
if (! isQConnected(project)) return false
598
641
if (QRegionProfileManager .getInstance().hasValidConnectionButNoActiveProfile(project)) return false
599
642
if (event.isManualCall()) return true
0 commit comments