Skip to content

Commit 785c985

Browse files
committed
feat(amazonq): support customization through language server
1 parent 011654d commit 785c985

File tree

7 files changed

+182
-72
lines changed

7 files changed

+182
-72
lines changed

packages/amazonq/src/app/inline/completion.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ import { SessionManager } from './sessionManager'
2626
import { RecommendationService } from './recommendationService'
2727
import {
2828
CodeWhispererConstants,
29+
CodeWhispererSource,
30+
connectCustomizationHandler,
2931
ReferenceHoverProvider,
3032
ReferenceInlineProvider,
3133
ReferenceLogViewProvider,
34+
selectCustomizationHandler,
3235
} from 'aws-core-vscode/codewhisperer'
3336

3437
export class InlineCompletionManager implements Disposable {
@@ -60,6 +63,31 @@ export class InlineCompletionManager implements Disposable {
6063
}
6164
}
6265

66+
public registerCustomization(client: LanguageClient) {
67+
commands.registerCommand(
68+
'aws.amazonq.connect',
69+
async (
70+
source: string,
71+
startUrl?: string,
72+
region?: string,
73+
customizationArn?: string,
74+
customizationNamePrefix?: string
75+
) => {
76+
return await connectCustomizationHandler(client)(
77+
source,
78+
startUrl,
79+
region,
80+
customizationArn,
81+
customizationNamePrefix
82+
)
83+
}
84+
)
85+
86+
commands.registerCommand('aws.amazonq.select', async (customization: any, source: CodeWhispererSource) => {
87+
return await selectCustomizationHandler(client)(customization, source)
88+
})
89+
}
90+
6391
public registerInlineCompletion() {
6492
const onInlineAcceptance = async (
6593
sessionId: string,

packages/amazonq/src/lsp/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol'
1414
import { Settings, oidcClientName, createServerOptions, globals, Experiments, getLogger } from 'aws-core-vscode/shared'
1515
import { activate } from './chat/activation'
1616
import { AmazonQResourcePaths } from './lspInstaller'
17+
import { notifyNewCustomizations } from 'aws-core-vscode/codewhisperer'
1718

1819
const localize = nls.loadMessageBundle()
1920

@@ -96,9 +97,13 @@ export async function startLanguageServer(
9697
await auth.init()
9798
const inlineManager = new InlineCompletionManager(client)
9899
inlineManager.registerInlineCompletion()
100+
inlineManager.registerCustomization(client)
99101
if (Experiments.instance.get('amazonqChatLSP', false)) {
100102
activate(client, encryptionKey, resourcePaths.mynahUI)
101103
}
104+
if (AuthUtil.instance.isValidEnterpriseSsoInUse()) {
105+
await notifyNewCustomizations(client)
106+
}
102107

103108
// Request handler for when the server wants to know about the clients auth connnection
104109
client.onRequest<ConnectionMetadata, Error>(notificationTypes.getConnectionMetadata.method, () => {

packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,26 @@ describe('InlineCompletionManager', () => {
253253
}
254254
})
255255
})
256-
})
256+
}),
257+
describe('registerCustomization', () => {
258+
beforeEach(() => {
259+
manager.registerCustomization(languageClient)
260+
})
261+
it('should register connect customization command', async () => {
262+
const connectCustomizationCall = registerCommandStub
263+
.getCalls()
264+
.find((call) => call.args[0] === 'aws.amazonq.connect')
265+
266+
assert(connectCustomizationCall, 'Connect customization command should be registered')
267+
})
268+
it('should register select customization command', async () => {
269+
const connectCustomizationCall = registerCommandStub
270+
.getCalls()
271+
.find((call) => call.args[0] === 'aws.amazonq.select')
272+
273+
assert(connectCustomizationCall, 'Select customization command should be registered')
274+
})
275+
})
257276

258277
describe('AmazonQInlineCompletionItemProvider', () => {
259278
describe('provideInlineCompletionItems', () => {

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

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker'
6969
import { parsePatch } from 'diff'
7070
import { createCodeIssueGroupingStrategyPrompter } from '../ui/prompters'
7171
import { cancel, confirm } from '../../shared/localizedText'
72+
import { LanguageClient } from 'vscode-languageclient'
7273

7374
const MessageTimeOut = 5_000
7475

@@ -237,15 +238,18 @@ export const showFileScan = Commands.declare(
237238
}
238239
)
239240

240-
export const selectCustomizationPrompt = Commands.declare(
241-
{ id: 'aws.amazonq.selectCustomization', compositeKey: { 1: 'source' } },
242-
() => async (_: VsCodeCommandArg, source: CodeWhispererSource) => {
241+
export const selectCustomizationHandler =
242+
(client?: LanguageClient) => async (_: VsCodeCommandArg, source: CodeWhispererSource) => {
243243
if (isBuilderIdConnection(AuthUtil.instance.conn)) {
244244
throw new Error(`Select Customizations are not supported with the Amazon Builder ID connection.`)
245245
}
246246
telemetry.ui_click.emit({ elementId: 'cw_selectCustomization_Cta' })
247-
void showCustomizationPrompt().then()
247+
void showCustomizationPrompt(client).then()
248248
}
249+
250+
export const selectCustomizationPrompt = Commands.declare(
251+
{ id: 'aws.amazonq.selectCustomization', compositeKey: { 1: 'source' } },
252+
() => selectCustomizationHandler()
249253
)
250254

251255
export const reconnect = Commands.declare(
@@ -263,6 +267,53 @@ export const showSsoSignIn = Commands.declare('aws.amazonq.sso', () => async ()
263267
// It can optionally set a customization too based on given values to match on
264268

265269
// This command is only declared and registered in Amazon Q if Q exists
270+
271+
export const connectCustomizationHandler =
272+
(client?: LanguageClient) =>
273+
async (
274+
source: string,
275+
startUrl?: string,
276+
region?: string,
277+
customizationArn?: string,
278+
customizationNamePrefix?: string
279+
) => {
280+
SsoAccessTokenProvider.authSource = source
281+
if (startUrl && region) {
282+
await connectToEnterpriseSso(startUrl, region)
283+
} else {
284+
await getStartUrl()
285+
}
286+
287+
// No customization match information given, exit early.
288+
if (!customizationArn && !customizationNamePrefix) {
289+
return
290+
}
291+
292+
let persistedCustomizations = getPersistedCustomizations()
293+
294+
// Check if any customizations have already been persisted.
295+
// If not, call `notifyNewCustomizations` to handle it then recheck.
296+
if (persistedCustomizations.length === 0) {
297+
await notifyNewCustomizations(client)
298+
persistedCustomizations = getPersistedCustomizations()
299+
}
300+
301+
// If given an ARN, assume a specific customization is desired and find an entry that matches it. Ignores the prefix logic.
302+
// Otherwise if only a prefix is given, find an entry that matches it.
303+
// Backwards compatible with previous implementation.
304+
const match = customizationArn
305+
? persistedCustomizations.find((c) => c.arn === customizationArn)
306+
: persistedCustomizations.find((c) => c.name?.startsWith(customizationNamePrefix as string))
307+
308+
// If no match is found, nothing to do :)
309+
if (!match) {
310+
getLogger().error(`No customization match found: arn=${customizationArn} prefix=${customizationNamePrefix}`)
311+
return
312+
}
313+
// Since we selected based on a match, we'll reuse the persisted values.
314+
await selectCustomization(match, client)
315+
}
316+
266317
export const connectWithCustomization = Commands.declare(
267318
{ id: 'aws.codeWhisperer.connect', compositeKey: { 0: 'source' } },
268319
/**
@@ -273,52 +324,7 @@ export const connectWithCustomization = Commands.declare(
273324
* customizationArn: select customization by ARN. If provided, `customizationNamePrefix` is ignored.
274325
* customizationNamePrefix: select customization by prefix, if `customizationArn` is `undefined`.
275326
*/
276-
() =>
277-
async (
278-
source: string,
279-
startUrl?: string,
280-
region?: string,
281-
customizationArn?: string,
282-
customizationNamePrefix?: string
283-
) => {
284-
SsoAccessTokenProvider.authSource = source
285-
if (startUrl && region) {
286-
await connectToEnterpriseSso(startUrl, region)
287-
} else {
288-
await getStartUrl()
289-
}
290-
291-
// No customization match information given, exit early.
292-
if (!customizationArn && !customizationNamePrefix) {
293-
return
294-
}
295-
296-
let persistedCustomizations = getPersistedCustomizations()
297-
298-
// Check if any customizations have already been persisted.
299-
// If not, call `notifyNewCustomizations` to handle it then recheck.
300-
if (persistedCustomizations.length === 0) {
301-
await notifyNewCustomizations()
302-
persistedCustomizations = getPersistedCustomizations()
303-
}
304-
305-
// If given an ARN, assume a specific customization is desired and find an entry that matches it. Ignores the prefix logic.
306-
// Otherwise if only a prefix is given, find an entry that matches it.
307-
// Backwards compatible with previous implementation.
308-
const match = customizationArn
309-
? persistedCustomizations.find((c) => c.arn === customizationArn)
310-
: persistedCustomizations.find((c) => c.name?.startsWith(customizationNamePrefix as string))
311-
312-
// If no match is found, nothing to do :)
313-
if (!match) {
314-
getLogger().error(
315-
`No customization match found: arn=${customizationArn} prefix=${customizationNamePrefix}`
316-
)
317-
return
318-
}
319-
// Since we selected based on a match, we'll reuse the persisted values.
320-
await selectCustomization(match)
321-
}
327+
() => connectCustomizationHandler()
322328
)
323329

324330
export const showLearnMore = Commands.declare(

packages/core/src/codewhisperer/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ export * as diagnosticsProvider from './service/diagnosticsProvider'
9898
export * from './ui/codeWhispererNodes'
9999
export { SecurityScanError, SecurityScanTimedOutError } from '../codewhisperer/models/errors'
100100
export * as CodeWhispererConstants from '../codewhisperer/models/constants'
101-
export { getSelectedCustomization, setSelectedCustomization, baseCustomization } from './util/customizationUtil'
101+
export {
102+
getSelectedCustomization,
103+
setSelectedCustomization,
104+
baseCustomization,
105+
notifyNewCustomizations,
106+
} from './util/customizationUtil'
102107
export { Container } from './service/serviceContainer'
103108
export * from './util/gitUtil'
104109
export * from './ui/prompters'

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { AuthUtil } from '../util/authUtil'
2727
import { submitFeedback } from '../../feedback/vue/submitFeedback'
2828
import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerCommands'
2929
import { isWeb } from '../../shared/extensionGlobals'
30+
import { Experiments } from '../../shared/settings'
3031

3132
export function createAutoSuggestions(running: boolean): DataQuickPickItem<'autoSuggestions'> {
3233
const labelResume = localize('AWS.codewhisperer.resumeCodeWhispererNode.label', 'Resume Auto-Suggestions')
@@ -129,11 +130,14 @@ export function createSelectCustomization(): DataQuickPickItem<'selectCustomizat
129130
const description =
130131
newCustomizationsAmount > 0 ? `${newCustomizationsAmount} new available` : `Using ${selectedCustomization.name}`
131132

133+
const selectCustomizationCommand = Experiments.instance.get('amazonqLSP', true)
134+
? () => vscode.commands.executeCommand('aws.amazonq.select')
135+
: () => selectCustomizationPrompt.execute(placeholder, cwQuickPickSource)
132136
return {
133137
data: 'selectCustomization',
134138
label: codicon`${icon} ${label}`,
135139
description: description,
136-
onClick: () => selectCustomizationPrompt.execute(placeholder, cwQuickPickSource),
140+
onClick: selectCustomizationCommand,
137141
} as DataQuickPickItem<'selectCustomization'>
138142
}
139143

0 commit comments

Comments
 (0)