Skip to content

Commit bd3bf40

Browse files
committed
feat(amazonq): paid tier
1 parent cdf1c2d commit bd3bf40

File tree

12 files changed

+98
-14
lines changed

12 files changed

+98
-14
lines changed

packages/amazonq/package.json

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,16 +388,21 @@
388388
"when": "view == aws.amazonq.AmazonQChatView",
389389
"group": "0_topAmazonQ@1"
390390
},
391-
{
392-
"command": "aws.amazonq.learnMore",
393-
"when": "view =~ /^aws\\.amazonq/",
394-
"group": "1_amazonQ@1"
395-
},
396391
{
397392
"command": "aws.amazonq.selectRegionProfile",
398393
"when": "view == aws.amazonq.AmazonQChatView && aws.amazonq.connectedSsoIdc == true",
399394
"group": "1_amazonQ@1"
400395
},
396+
{
397+
"command": "aws.amazonq.manageSubscription",
398+
"when": "(view == aws.amazonq.AmazonQChatView) && aws.codewhisperer.connected",
399+
"group": "1_amazonQ@2"
400+
},
401+
{
402+
"command": "aws.amazonq.learnMore",
403+
"when": "view =~ /^aws\\.amazonq/",
404+
"group": "1_amazonQ@3"
405+
},
401406
{
402407
"command": "aws.amazonq.signout",
403408
"when": "(view == aws.amazonq.AmazonQChatView) && aws.codewhisperer.connected && !aws.isSageMakerUnifiedStudio",
@@ -679,6 +684,13 @@
679684
"category": "%AWS.amazonq.title%",
680685
"icon": "$(question)"
681686
},
687+
{
688+
"command": "aws.amazonq.manageSubscription",
689+
"title": "%AWS.command.manageSubscription%",
690+
"category": "%AWS.amazonq.title%",
691+
"icon": "$(gear)",
692+
"enablement": "aws.codewhisperer.connected && !aws.amazonq.connectedSsoIdc"
693+
},
682694
{
683695
"command": "aws.amazonq.signout",
684696
"title": "%AWS.command.codewhisperer.signout%",

packages/amazonq/src/lsp/auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export class AmazonQLspAuth {
9696
token,
9797
})
9898

99+
// "aws/credentials/token/update"
100+
// https://github.com/aws/language-servers/blob/44d81f0b5754747d77bda60b40cc70950413a737/core/aws-lsp-core/src/credentials/credentialsProvider.ts#L27
99101
await this.client.sendRequest(bearerCredentialsUpdateRequestType.method, request)
100102

101103
this.client.info(`UpdateBearerToken: ${JSON.stringify(request)}`)

packages/amazonq/src/lsp/chat/activation.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { window } from 'vscode'
77
import { LanguageClient } from 'vscode-languageclient'
88
import { AmazonQChatViewProvider } from './webviewProvider'
9-
import { registerCommands } from './commands'
9+
import { focusAmazonQPanel, registerCommands } from './commands'
1010
import { registerLanguageServerEventListener, registerMessageListeners } from './messages'
1111
import { Commands, getLogger, globals, undefinedIfEmpty } from 'aws-core-vscode/shared'
1212
import { activate as registerLegacyChatListeners } from '../../app/chat/activation'
@@ -73,6 +73,18 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu
7373
customization: undefinedIfEmpty(getSelectedCustomization().arn),
7474
})
7575
}),
76+
Commands.register('aws.amazonq.manageSubscription', () => {
77+
focusAmazonQPanel().catch((e) => languageClient.error(`[VSCode Client] focusAmazonQPanel() failed`))
78+
79+
languageClient
80+
.sendRequest('workspace/executeCommand', {
81+
command: 'aws/chat/manageSubscription',
82+
// arguments: [],
83+
})
84+
.catch((e) => {
85+
getLogger('amazonqLsp').error('failed request: aws/chat/manageSubscription: %O', e)
86+
})
87+
}),
7688
globals.logOutputChannel.onDidChangeLogLevel((logLevel) => {
7789
getLogger('amazonqLsp').info(`Local log level changed to ${logLevel}, notifying LSP`)
7890
void pushConfigUpdate(languageClient, {

packages/amazonq/src/lsp/chat/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ function registerGenericCommand(commandName: string, genericCommand: string, pro
125125
*
126126
* Instead, we just create our own as a temporary solution
127127
*/
128-
async function focusAmazonQPanel() {
128+
export async function focusAmazonQPanel() {
129129
await Commands.tryExecute('aws.amazonq.AmazonQChatView.focus')
130130
await Commands.tryExecute('aws.amazonq.AmazonCommonAuth.focus')
131131
}

packages/amazonq/src/lsp/chat/messages.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import * as jose from 'jose'
6363
import { AmazonQChatViewProvider } from './webviewProvider'
6464
import { AuthUtil, ReferenceLogViewProvider } from 'aws-core-vscode/codewhisperer'
6565
import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared'
66+
import { credentialsValidation } from 'aws-core-vscode/auth'
6667
import {
6768
DefaultAmazonQAppInitContext,
6869
messageDispatcher,
@@ -72,6 +73,7 @@ import {
7273
} from 'aws-core-vscode/amazonq'
7374
import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry'
7475
import { isValidResponseError } from './error'
76+
import { focusAmazonQPanel } from './commands'
7577

7678
export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) {
7779
languageClient.info(
@@ -328,13 +330,33 @@ export function registerMessageListeners(
328330
}
329331
break
330332
case buttonClickRequestType.method: {
333+
if (message.params.buttonId === 'paidtier-upgrade-q') {
334+
focusAmazonQPanel().catch((e) => languageClient.error(`[VSCode Client] focusAmazonQPanel() failed`))
335+
336+
const accountId = await vscode.window.showInputBox({
337+
title: 'Upgrade Amazon Q',
338+
prompt: 'Enter your 12-digit AWS account ID',
339+
placeHolder: '111111111111',
340+
validateInput: credentialsValidation.validateAwsAccount,
341+
})
342+
343+
if (accountId) {
344+
languageClient.sendRequest('workspace/executeCommand', {
345+
command: 'aws/chat/manageSubscription',
346+
arguments: [accountId],
347+
})
348+
} else {
349+
languageClient.error('[VSCode Client] user canceled or did not input AWS account id')
350+
}
351+
}
352+
331353
const buttonResult = await languageClient.sendRequest<ButtonClickResult>(
332354
buttonClickRequestType.method,
333355
message.params
334356
)
335357
if (!buttonResult.success) {
336358
languageClient.error(
337-
`[VSCode Client] Failed to execute action associated with button with reason: ${buttonResult.failureReason}`
359+
`[VSCode Client] Failed to execute button action: ${buttonResult.failureReason}`
338360
)
339361
}
340362
break
@@ -433,6 +455,8 @@ export function registerMessageListeners(
433455
languageClient.onRequest<ShowDocumentParams, ShowDocumentResult>(
434456
ShowDocumentRequest.method,
435457
async (params: ShowDocumentParams): Promise<ShowDocumentParams | ResponseError<ShowDocumentResult>> => {
458+
focusAmazonQPanel().catch((e) => languageClient.error(`[VSCode Client] focusAmazonQPanel() failed`))
459+
436460
try {
437461
const uri = vscode.Uri.parse(params.uri)
438462

packages/core/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"AWS.command.codecatalyst.login": "Connect to CodeCatalyst",
138138
"AWS.command.codecatalyst.logout": "Sign out of CodeCatalyst",
139139
"AWS.command.codecatalyst.signout": "Sign Out",
140+
"AWS.command.manageSubscription": "Manage Q Developer Pro Subscription",
140141
"AWS.command.amazonq.explainCode": "Explain",
141142
"AWS.command.amazonq.refactorCode": "Refactor",
142143
"AWS.command.amazonq.fixCode": "Fix",

packages/core/src/auth/credentials/validation.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ async function validateProfileName(profileName: SectionName) {
127127
}
128128
}
129129

130+
export function validateAwsAccount(s: string): string | undefined {
131+
// AWS account IDs are exactly 12 digits
132+
if (!/^\d{12}$/.test(s)) {
133+
return 'Enter a valid 12-digit AWS account ID'
134+
}
135+
return undefined
136+
}
137+
130138
// All shared credentials keys
131139
const sharedCredentialsKeysSet = new Set(Object.values(SharedCredentialsKeys))
132140

packages/core/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ export { Auth } from './auth'
2424
export { CredentialsStore } from './credentials/store'
2525
export { LoginManager } from './deprecated/loginManager'
2626
export * as AuthUtils from './utils'
27+
export * as credentialsValidation from './credentials/validation'

packages/core/src/codewhisperer/ui/codeWhispererNodes.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,18 @@ export function createGettingStarted(): DataQuickPickItem<'gettingStarted'> {
176176
} as DataQuickPickItem<'gettingStarted'>
177177
}
178178

179+
export function createManageSubscription(): DataQuickPickItem<'manageSubscription'> {
180+
const label = localize('AWS.command.manageSubscription', 'Manage Q Developer Pro Subscription')
181+
// const kind = AuthUtil.instance.isBuilderIdInUse() ? 'AWS Builder ID' : 'IAM Identity Center'
182+
183+
return {
184+
data: 'manageSubscription',
185+
label: label,
186+
iconPath: getIcon('vscode-link-external'),
187+
onClick: () => Commands.tryExecute('aws.amazonq.manageSubscription'),
188+
} as DataQuickPickItem<'manageSubscription'>
189+
}
190+
179191
export function createSignout(): DataQuickPickItem<'signout'> {
180192
const label = localize('AWS.codewhisperer.signoutNode.label', 'Sign Out')
181193
const icon = getIcon('vscode-export')

packages/core/src/codewhisperer/ui/statusBarMenu.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
createSelectCustomization,
1212
createReconnect,
1313
createGettingStarted,
14+
createManageSubscription,
1415
createSignout,
1516
createSeparator,
1617
createSettingsNode,
@@ -106,7 +107,7 @@ export function getQuickPickItems(): DataQuickPickItem<string>[] {
106107
createSettingsNode(),
107108
...(isUsingEnterpriseSso && regionProfile ? [createSelectRegionProfileNode(regionProfile)] : []),
108109
...(AuthUtil.instance.isConnected() && !hasVendedIamCredentials() && !hasVendedCredentialsFromMetadata()
109-
? [createSignout()]
110+
? [createManageSubscription(), createSignout()]
110111
: []),
111112
]
112113

packages/core/src/login/webview/vue/login.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@
108108
@toggle="toggleItemSelection"
109109
:isSelected="selectedLoginOption === LoginOption.BUILDER_ID"
110110
:itemId="LoginOption.BUILDER_ID"
111-
:itemText="'with Builder ID, a personal profile from AWS'"
112-
:itemTitle="'Use for Free'"
111+
:itemText="'Free to start with a Builder ID.'"
112+
:itemTitle="'Personal account'"
113113
:itemType="LoginOption.BUILDER_ID"
114114
class="selectable-item bottomMargin"
115115
></SelectableItem>
@@ -118,8 +118,8 @@
118118
@toggle="toggleItemSelection"
119119
:isSelected="selectedLoginOption === LoginOption.ENTERPRISE_SSO"
120120
:itemId="LoginOption.ENTERPRISE_SSO"
121-
:itemText="''"
122-
:itemTitle="'Use with Pro license'"
121+
:itemText="'Best for individual teams or organizations.'"
122+
:itemTitle="'Company account'"
123123
:itemType="LoginOption.ENTERPRISE_SSO"
124124
class="selectable-item bottomMargin"
125125
></SelectableItem>

packages/core/src/test/codewhisperer/commands/basicCommands.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
createGettingStarted,
4343
createGitHubNode,
4444
createLearnMore,
45+
createManageSubscription,
4546
createOpenReferenceLog,
4647
createReconnect,
4748
createSecurityScan,
@@ -445,7 +446,13 @@ describe('CodeWhisperer-basicCommands', function () {
445446
sinon.stub(AuthUtil.instance, 'isConnected').returns(true)
446447

447448
getTestWindow().onDidShowQuickPick((e) => {
448-
e.assertContainsItems(createReconnect(), createLearnMore(), ...genericItems(), createSignout())
449+
e.assertContainsItems(
450+
createReconnect(),
451+
createLearnMore(),
452+
...genericItems(),
453+
createManageSubscription(),
454+
createSignout()
455+
)
449456
e.dispose() // skip needing to select an item to continue
450457
})
451458

@@ -465,6 +472,7 @@ describe('CodeWhisperer-basicCommands', function () {
465472
switchToAmazonQNode(),
466473
...genericItems(),
467474
createSettingsNode(),
475+
createManageSubscription(),
468476
createSignout()
469477
)
470478
e.dispose() // skip needing to select an item to continue
@@ -489,6 +497,7 @@ describe('CodeWhisperer-basicCommands', function () {
489497
switchToAmazonQNode(),
490498
...genericItems(),
491499
createSettingsNode(),
500+
createManageSubscription(),
492501
createSignout()
493502
)
494503
e.dispose() // skip needing to select an item to continue
@@ -515,6 +524,7 @@ describe('CodeWhisperer-basicCommands', function () {
515524
...genericItems(),
516525
createSeparator(),
517526
createSettingsNode(),
527+
createManageSubscription(),
518528
createSignout(),
519529
])
520530
e.dispose() // skip needing to select an item to continue
@@ -537,6 +547,7 @@ describe('CodeWhisperer-basicCommands', function () {
537547
switchToAmazonQNode(),
538548
...genericItems(),
539549
createSettingsNode(),
550+
createManageSubscription(),
540551
createSignout()
541552
)
542553
e.dispose()

0 commit comments

Comments
 (0)