Skip to content

Commit 837699d

Browse files
committed
feat(amazonq): Use new auth class using flare identity server for Amazon Q
1 parent 5c37ceb commit 837699d

File tree

14 files changed

+250
-124
lines changed

14 files changed

+250
-124
lines changed

docs/lsp.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ sequenceDiagram
4848
3. Enable the lsp experiment:
4949
```
5050
"aws.experiments": {
51-
"amazonqLSP": true,
5251
"amazonqLSPInline": true, // optional: enables inline completion from flare
5352
"amazonqLSPChat": true // optional: enables chat from flare
5453
}

packages/amazonq/src/api.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseReques
88
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
99
import { ChatSession } from 'aws-core-vscode/codewhispererChat'
1010
import { api } from 'aws-core-vscode/amazonq'
11+
import { getLogger } from 'aws-core-vscode/shared'
1112

1213
export default {
1314
chatApi: {
@@ -26,8 +27,25 @@ export default {
2627
await AuthUtil.instance.showReauthenticatePrompt()
2728
}
2829
},
30+
/**
31+
* @deprecated use getAuthState() instead
32+
*
33+
* Legacy function for callers who expect auth state to be granular amongst Q features.
34+
* Auth state is consistent between features, so getAuthState() can be consumed safely for all features.
35+
*
36+
*/
2937
async getChatAuthState() {
30-
return AuthUtil.instance.getChatAuthState()
38+
getLogger().warn('Warning: getChatAuthState() is deprecated. Use getAuthState() instead.')
39+
const state = AuthUtil.instance.getAuthState()
40+
const convertedState = state === 'notConnected' ? 'disconnected' : state
41+
return {
42+
codewhispererCore: convertedState,
43+
codewhispererChat: convertedState,
44+
amazonQ: convertedState,
45+
}
46+
},
47+
getAuthState() {
48+
return AuthUtil.instance.getAuthState()
3149
},
3250
},
3351
} satisfies api

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export async function activate(context: ExtensionContext) {
2727

2828
const setupLsp = funcUtil.debounce(async () => {
2929
void amazonq.LspController.instance.trySetupLsp(context, {
30-
startUrl: AuthUtil.instance.startUrl,
30+
startUrl: AuthUtil.instance.connection?.startUrl,
3131
maxIndexSize: CodeWhispererSettings.instance.getMaxIndexSize(),
3232
isVectorIndexEnabled: CodeWhispererSettings.instance.isLocalIndexEnabled(),
3333
})
@@ -44,8 +44,6 @@ export async function activate(context: ExtensionContext) {
4444
amazonq.walkthroughSecurityScanExample.register(),
4545
amazonq.openAmazonQWalkthrough.register(),
4646
amazonq.listCodeWhispererCommandsWalkthrough.register(),
47-
amazonq.focusAmazonQPanel.register(),
48-
amazonq.focusAmazonQPanelKeybinding.register(),
4947
amazonq.tryChatCodeLensCommand.register(),
5048
vscode.workspace.onDidChangeConfiguration(async (configurationChangeEvent) => {
5149
if (configurationChangeEvent.affectsConfiguration('amazonQ.workspaceIndex')) {

packages/amazonq/src/extension.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { AuthUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth'
6+
import { CredentialsStore, LoginManager, authUtils, initializeAuth } from 'aws-core-vscode/auth'
77
import { activate as activateCodeWhisperer, shutdown as shutdownCodeWhisperer } from 'aws-core-vscode/codewhisperer'
88
import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode'
99
import { CommonAuthWebview } from 'aws-core-vscode/login'
@@ -33,6 +33,7 @@ import {
3333
maybeShowMinVscodeWarning,
3434
Experiments,
3535
isSageMaker,
36+
Commands,
3637
} from 'aws-core-vscode/shared'
3738
import { ExtStartUpSources } from 'aws-core-vscode/telemetry'
3839
import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils'
@@ -114,14 +115,14 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
114115

115116
await initializeAuth(globals.loginManager)
116117

118+
await activateAmazonqLsp(context)
119+
117120
const extContext = {
118121
extensionContext: context,
119122
}
123+
120124
// This contains every lsp agnostic things (auth, security scan, code scan)
121125
await activateCodeWhisperer(extContext as ExtContext)
122-
if (Experiments.instance.get('amazonqLSP', false)) {
123-
await activateAmazonqLsp(context)
124-
}
125126

126127
if (!Experiments.instance.get('amazonqLSPInline', false)) {
127128
await activateInlineCompletion()
@@ -130,6 +131,10 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
130131
// Generic extension commands
131132
registerGenericCommands(context, amazonQContextPrefix)
132133

134+
// Create status bar and reference log UI elements
135+
void Commands.tryExecute('aws.amazonq.refreshStatusBar')
136+
void Commands.tryExecute('aws.amazonq.updateReferenceLog')
137+
133138
// Amazon Q specific commands
134139
registerCommands(context)
135140

@@ -156,7 +161,7 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
156161
// reload webviews
157162
await vscode.commands.executeCommand('workbench.action.webview.reloadWebviewAction')
158163

159-
if (AuthUtils.ExtensionUse.instance.isFirstUse()) {
164+
if (authUtils.ExtensionUse.instance.isFirstUse()) {
160165
// Give time for the extension to finish initializing.
161166
globals.clock.setTimeout(async () => {
162167
CommonAuthWebview.authSource = ExtStartUpSources.firstStartUp
@@ -166,7 +171,7 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is
166171

167172
context.subscriptions.push(
168173
Experiments.instance.onDidChange(async (event) => {
169-
if (event.key === 'amazonqLSP' || event.key === 'amazonqChatLSP' || event.key === 'amazonqLSPInline') {
174+
if (event.key === 'amazonqChatLSP' || event.key === 'amazonqLSPInline') {
170175
await vscode.window
171176
.showInformationMessage(
172177
'Amazon Q LSP setting has changed. Reload VS Code for the changes to take effect.',

packages/amazonq/src/lsp/activation.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
*/
55

66
import vscode from 'vscode'
7-
import { startLanguageServer } from './client'
7+
import { clientId, encryptionKey, startLanguageServer } from './client'
88
import { AmazonQLspInstaller } from './lspInstaller'
99
import { lspSetupStage, ToolkitError } from 'aws-core-vscode/shared'
10+
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
11+
import { auth2 } from 'aws-core-vscode/auth'
1012

11-
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
13+
export async function activate(ctx: vscode.ExtensionContext) {
1214
try {
13-
await lspSetupStage('all', async () => {
15+
const client = await lspSetupStage('all', async () => {
1416
const installResult = await new AmazonQLspInstaller().resolve()
15-
await lspSetupStage('launch', async () => await startLanguageServer(ctx, installResult.resourcePaths))
17+
return await lspSetupStage('launch', () => startLanguageServer(ctx, installResult.resourcePaths))
1618
})
19+
AuthUtil.create(new auth2.LanguageClientAuth(client, clientId, encryptionKey))
20+
await AuthUtil.instance.restore()
1721
} catch (err) {
1822
const e = err as ToolkitError
1923
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)

packages/amazonq/src/lsp/client.ts

Lines changed: 101 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,46 @@
66
import vscode, { env, version } from 'vscode'
77
import * as nls from 'vscode-nls'
88
import * as crypto from 'crypto'
9+
import * as jose from 'jose'
910
import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient'
1011
import { InlineCompletionManager } from '../app/inline/completion'
11-
import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth'
1212
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
13-
import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol'
14-
import { Settings, oidcClientName, createServerOptions, globals, Experiments, Commands } from 'aws-core-vscode/shared'
13+
import {
14+
ConnectionMetadata,
15+
GetSsoTokenProgress,
16+
GetSsoTokenProgressToken,
17+
GetSsoTokenProgressType,
18+
MessageActionItem,
19+
ShowDocumentParams,
20+
ShowDocumentRequest,
21+
ShowDocumentResult,
22+
ShowMessageRequest,
23+
ShowMessageRequestParams,
24+
} from '@aws/language-server-runtimes/protocol'
25+
import {
26+
Settings,
27+
oidcClientName,
28+
createServerOptions,
29+
globals,
30+
Experiments,
31+
Commands,
32+
openUrl,
33+
getLogger,
34+
} from 'aws-core-vscode/shared'
1535
import { activate } from './chat/activation'
1636
import { AmazonQResourcePaths } from './lspInstaller'
37+
import { auth2 } from 'aws-core-vscode/auth'
1738

1839
const localize = nls.loadMessageBundle()
1940

41+
export const clientId = 'amazonq'
42+
export const clientName = oidcClientName()
43+
export const encryptionKey = crypto.randomBytes(32)
44+
2045
export async function startLanguageServer(
2146
extensionContext: vscode.ExtensionContext,
2247
resourcePaths: AmazonQResourcePaths
23-
) {
48+
): Promise<LanguageClient> {
2449
const toDispose = extensionContext.subscriptions
2550

2651
const serverModule = resourcePaths.lsp
@@ -39,8 +64,6 @@ export async function startLanguageServer(
3964
})
4065

4166
const documentSelector = [{ scheme: 'file', language: '*' }]
42-
43-
const clientId = 'amazonq'
4467
const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`)
4568

4669
// Options to control the language client
@@ -80,57 +103,89 @@ export async function startLanguageServer(
80103
}),
81104
}
82105

83-
const client = new LanguageClient(
84-
clientId,
85-
localize('amazonq.server.name', 'Amazon Q Language Server'),
86-
serverOptions,
87-
clientOptions
88-
)
106+
const lspName = localize('amazonq.server.name', 'Amazon Q Language Server')
107+
const client = new LanguageClient(clientId, lspName, serverOptions, clientOptions)
89108

90109
const disposable = client.start()
91110
toDispose.push(disposable)
92111

93-
const auth = new AmazonQLspAuth(client)
112+
await client.onReady()
94113

95-
return client.onReady().then(async () => {
96-
// Request handler for when the server wants to know about the clients auth connnection. Must be registered before the initial auth init call
97-
client.onRequest<ConnectionMetadata, Error>(notificationTypes.getConnectionMetadata.method, () => {
98-
return {
99-
sso: {
100-
startUrl: AuthUtil.instance.auth.startUrl,
101-
},
102-
}
103-
})
104-
await auth.refreshConnection()
105-
106-
if (Experiments.instance.get('amazonqLSPInline', false)) {
107-
const inlineManager = new InlineCompletionManager(client)
108-
inlineManager.registerInlineCompletion()
109-
toDispose.push(
110-
inlineManager,
111-
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
112-
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
113-
}),
114-
vscode.workspace.onDidCloseTextDocument(async () => {
115-
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')
116-
})
117-
)
114+
// Request handler for when the server wants to know about the clients auth connnection. Must be registered before the initial auth init call
115+
client.onRequest<ConnectionMetadata, Error>(auth2.notificationTypes.getConnectionMetadata.method, () => {
116+
return {
117+
sso: {
118+
startUrl: AuthUtil.instance.connection?.startUrl,
119+
},
118120
}
121+
})
119122

120-
if (Experiments.instance.get('amazonqChatLSP', false)) {
121-
activate(client, encryptionKey, resourcePaths.ui)
123+
client.onRequest<ShowDocumentResult, Error>(ShowDocumentRequest.method, async (params: ShowDocumentParams) => {
124+
try {
125+
return { success: await openUrl(vscode.Uri.parse(params.uri), lspName) }
126+
} catch (err: any) {
127+
getLogger().error(`Failed to open document for LSP: ${lspName}, error: %s`, err)
128+
return { success: false }
122129
}
130+
})
131+
132+
client.onRequest<MessageActionItem | null, Error>(
133+
ShowMessageRequest.method,
134+
async (params: ShowMessageRequestParams) => {
135+
const actions = params.actions?.map((a) => a.title) ?? []
136+
const response = await vscode.window.showInformationMessage(params.message, { modal: true }, ...actions)
137+
return params.actions?.find((a) => a.title === response) ?? (undefined as unknown as null)
138+
}
139+
)
123140

124-
const refreshInterval = auth.startTokenRefreshInterval()
141+
let promise: Promise<void> | undefined
142+
let resolver: () => void = () => {}
143+
client.onProgress(GetSsoTokenProgressType, GetSsoTokenProgressToken, async (partialResult: GetSsoTokenProgress) => {
144+
const decryptedKey = await jose.compactDecrypt(partialResult as unknown as string, encryptionKey)
145+
const val: GetSsoTokenProgress = JSON.parse(decryptedKey.plaintext.toString())
125146

147+
if (val.state === 'InProgress') {
148+
if (promise) {
149+
resolver()
150+
}
151+
promise = new Promise<void>((resolve) => {
152+
resolver = resolve
153+
})
154+
} else {
155+
resolver()
156+
promise = undefined
157+
return
158+
}
159+
160+
void vscode.window.withProgress(
161+
{
162+
cancellable: true,
163+
location: vscode.ProgressLocation.Notification,
164+
title: val.message,
165+
},
166+
async (_) => {
167+
await promise
168+
}
169+
)
170+
})
171+
172+
if (Experiments.instance.get('amazonqLSPInline', false)) {
173+
const inlineManager = new InlineCompletionManager(client)
174+
inlineManager.registerInlineCompletion()
126175
toDispose.push(
127-
AuthUtil.instance.auth.onDidChangeActiveConnection(async () => {
128-
await auth.refreshConnection()
176+
inlineManager,
177+
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
178+
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
129179
}),
130-
AuthUtil.instance.auth.onDidDeleteConnection(async () => {
131-
client.sendNotification(notificationTypes.deleteBearerToken.method)
132-
}),
133-
{ dispose: () => clearInterval(refreshInterval) }
180+
vscode.workspace.onDidCloseTextDocument(async () => {
181+
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')
182+
})
134183
)
135-
})
184+
}
185+
186+
if (Experiments.instance.get('amazonqChatLSP', false)) {
187+
activate(client, encryptionKey, resourcePaths.ui)
188+
}
189+
190+
return client
136191
}

packages/core/src/amazonq/extApi.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import vscode from 'vscode'
77
import { VSCODE_EXTENSION_ID } from '../shared/extensions'
88
import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client'
99
import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming'
10-
import { FeatureAuthState } from '../codewhisperer/util/authUtil'
10+
import { auth2 } from 'aws-core-vscode/auth'
1111
import { ToolkitError } from '../shared/errors'
1212

13+
/**
14+
* @deprecated, for backwards comaptibility only.
15+
*/
16+
type OldAuthState = 'disconnected' | 'expired' | 'connected'
17+
1318
/**
1419
* This interface is used and exported by the amazon q extension. If you make a change here then
1520
* update the corresponding api implementation in packages/amazonq/src/api.ts
@@ -21,7 +26,15 @@ export interface api {
2126
}
2227
authApi: {
2328
reauthIfNeeded(): Promise<void>
24-
getChatAuthState(): Promise<FeatureAuthState>
29+
/**
30+
* @deprecated, for backwards comaptibility only.
31+
*/
32+
getChatAuthState(): Promise<{
33+
codewhispererCore: OldAuthState
34+
codewhispererChat: OldAuthState
35+
amazonQ: OldAuthState
36+
}>
37+
getAuthState(): auth2.AuthState
2538
}
2639
}
2740

0 commit comments

Comments
 (0)