Skip to content

Commit d6b3572

Browse files
authored
Merge branch 'feature/amazonqLSP-auth' into feature/amazonqLSP-auth
2 parents 837699d + e920a3a commit d6b3572

File tree

14 files changed

+656
-121
lines changed

14 files changed

+656
-121
lines changed

package-lock.json

Lines changed: 13 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Users might be bound to a customization which they dont have access with the selected profile and it causes service throwing 403 when using inline suggestion and chat features"
4+
}

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

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
COPY_TO_CLIPBOARD,
1212
AuthFollowUpType,
1313
DISCLAIMER_ACKNOWLEDGED,
14+
UiMessageResultParams,
1415
} from '@aws/chat-client-ui-types'
1516
import {
1617
ChatResult,
@@ -21,30 +22,42 @@ import {
2122
QuickActionResult,
2223
QuickActionParams,
2324
insertToCursorPositionNotificationType,
25+
ErrorCodes,
26+
ResponseError,
27+
openTabRequestType,
28+
getSerializedChatRequestType,
29+
listConversationsRequestType,
30+
conversationClickRequestType,
31+
ShowSaveFileDialogRequestType,
32+
ShowSaveFileDialogParams,
33+
LSPErrorCodes,
34+
tabBarActionRequestType,
2435
} from '@aws/language-server-runtimes/protocol'
2536
import { v4 as uuidv4 } from 'uuid'
2637
import * as vscode from 'vscode'
27-
import { Disposable, LanguageClient, Position, State, TextDocumentIdentifier } from 'vscode-languageclient'
38+
import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vscode-languageclient'
2839
import * as jose from 'jose'
2940
import { AmazonQChatViewProvider } from './webviewProvider'
3041
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
3142
import { AmazonQPromptSettings, messages } from 'aws-core-vscode/shared'
3243

3344
export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) {
34-
languageClient.onDidChangeState(({ oldState, newState }) => {
35-
if (oldState === State.Starting && newState === State.Running) {
36-
languageClient.info(
37-
'Language client received initializeResult from server:',
38-
JSON.stringify(languageClient.initializeResult)
39-
)
45+
languageClient.info(
46+
'Language client received initializeResult from server:',
47+
JSON.stringify(languageClient.initializeResult)
48+
)
4049

41-
const chatOptions = languageClient.initializeResult?.awsServerCapabilities?.chatOptions
50+
const chatOptions = languageClient.initializeResult?.awsServerCapabilities?.chatOptions
4251

43-
void provider.webview?.postMessage({
44-
command: CHAT_OPTIONS,
45-
params: chatOptions,
46-
})
47-
}
52+
// Enable the history/export feature flags
53+
chatOptions.history = true
54+
chatOptions.export = true
55+
56+
provider.onDidResolveWebview(() => {
57+
void provider.webview?.postMessage({
58+
command: CHAT_OPTIONS,
59+
params: chatOptions,
60+
})
4861
})
4962

5063
languageClient.onTelemetry((e) => {
@@ -60,6 +73,7 @@ export function registerMessageListeners(
6073
provider.webview?.onDidReceiveMessage(async (message) => {
6174
languageClient.info(`[VSCode Client] Received ${JSON.stringify(message)} from chat`)
6275

76+
const webview = provider.webview
6377
switch (message.command) {
6478
case COPY_TO_CLIPBOARD:
6579
languageClient.info('[VSCode Client] Copy to clipboard event received')
@@ -172,6 +186,11 @@ export function registerMessageListeners(
172186
)
173187
break
174188
}
189+
case listConversationsRequestType.method:
190+
case conversationClickRequestType.method:
191+
case tabBarActionRequestType.method:
192+
await resolveChatResponse(message.command, message.params, languageClient, webview)
193+
break
175194
case followUpClickNotificationType.method:
176195
if (!isValidAuthFollowUpType(message.params.followUp.type)) {
177196
languageClient.sendNotification(followUpClickNotificationType.method, message.params)
@@ -184,6 +203,89 @@ export function registerMessageListeners(
184203
break
185204
}
186205
}, undefined)
206+
207+
const registerHandlerWithResponseRouter = (command: string) => {
208+
const handler = async (params: any, _: any) => {
209+
const mapErrorType = (type: string | undefined): number => {
210+
switch (type) {
211+
case 'InvalidRequest':
212+
return ErrorCodes.InvalidRequest
213+
case 'InternalError':
214+
return ErrorCodes.InternalError
215+
case 'UnknownError':
216+
default:
217+
return ErrorCodes.UnknownErrorCode
218+
}
219+
}
220+
const requestId = uuidv4()
221+
222+
void provider.webview?.postMessage({
223+
requestId: requestId,
224+
command: command,
225+
params: params,
226+
})
227+
const responsePromise = new Promise<UiMessageResultParams | undefined>((resolve, reject) => {
228+
const timeout = setTimeout(() => {
229+
disposable?.dispose()
230+
reject(new Error('Request timed out'))
231+
}, 30000)
232+
233+
const disposable = provider.webview?.onDidReceiveMessage((message: any) => {
234+
if (message.requestId === requestId) {
235+
clearTimeout(timeout)
236+
disposable?.dispose()
237+
resolve(message.params)
238+
}
239+
})
240+
})
241+
242+
const result = await responsePromise
243+
244+
if (result?.success) {
245+
return result.result
246+
} else {
247+
return new ResponseError(
248+
mapErrorType(result?.error.type),
249+
result?.error.message ?? 'No response from client'
250+
)
251+
}
252+
}
253+
254+
languageClient.onRequest(command, handler)
255+
}
256+
257+
registerHandlerWithResponseRouter(openTabRequestType.method)
258+
registerHandlerWithResponseRouter(getSerializedChatRequestType.method)
259+
260+
languageClient.onRequest(ShowSaveFileDialogRequestType.method, async (params: ShowSaveFileDialogParams) => {
261+
const filters: Record<string, string[]> = {}
262+
const formatMappings = [
263+
{ format: 'markdown', key: 'Markdown', extensions: ['md'] },
264+
{ format: 'html', key: 'HTML', extensions: ['html'] },
265+
]
266+
267+
for (const format of params.supportedFormats ?? []) {
268+
const mapping = formatMappings.find((m) => m.format === format)
269+
if (mapping) {
270+
filters[mapping.key] = mapping.extensions
271+
}
272+
}
273+
274+
const saveAtUri = params.defaultUri ? vscode.Uri.parse(params.defaultUri) : vscode.Uri.file('export-chat.md')
275+
const targetUri = await vscode.window.showSaveDialog({
276+
filters,
277+
defaultUri: saveAtUri,
278+
title: 'Export',
279+
})
280+
281+
if (!targetUri) {
282+
return new ResponseError(LSPErrorCodes.RequestFailed, 'Export failed')
283+
}
284+
285+
return {
286+
targetUri: targetUri.toString(),
287+
}
288+
})
187289
}
188290

189291
function isServerEvent(command: string) {
@@ -258,3 +360,16 @@ async function handleCompleteResult<T>(
258360
})
259361
disposable.dispose()
260362
}
363+
364+
async function resolveChatResponse(
365+
requestMethod: string,
366+
params: any,
367+
languageClient: LanguageClient,
368+
webview: vscode.Webview | undefined
369+
) {
370+
const result = await languageClient.sendRequest(requestMethod, params)
371+
void webview?.postMessage({
372+
command: requestMethod,
373+
params: result,
374+
})
375+
}

packages/amazonq/test/unit/amazonq/lsp/chat/messages.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('registerMessageListeners', () => {
2626
info: sandbox.stub(),
2727
error: errorStub,
2828
sendNotification: sandbox.stub(),
29+
onRequest: sandbox.stub(),
2930
} as unknown as LanguageClient
3031

3132
provider = {

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,8 @@
441441
"devDependencies": {
442442
"@aws-sdk/types": "^3.13.1",
443443
"@aws/chat-client-ui-types": "^0.1.12",
444-
"@aws/language-server-runtimes": "^0.2.49",
445-
"@aws/language-server-runtimes-types": "^0.1.10",
444+
"@aws/language-server-runtimes": "^0.2.58",
445+
"@aws/language-server-runtimes-types": "^0.1.13",
446446
"@cspotcode/source-map-support": "^0.8.1",
447447
"@sinonjs/fake-timers": "^10.0.2",
448448
"@types/adm-zip": "^0.4.34",

packages/core/src/amazonq/commons/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { DiffTreeFileInfo } from '../webview/ui/diffTree/types'
1111
import { Messenger } from './connector/baseMessenger'
1212
import { FeatureClient } from '../client/client'
1313
import { TelemetryHelper } from '../util/telemetryHelper'
14+
import { MynahUI } from '@aws/mynah-ui'
1415

1516
export enum FollowUpTypes {
1617
// UnitTestGeneration
@@ -164,3 +165,10 @@ export enum MetricDataResult {
164165
Error = 'Error',
165166
LlmFailure = 'LLMFailure',
166167
}
168+
169+
/**
170+
* Note: Passing a reference around allows us to lazily inject mynah UI into
171+
* connectors and handlers. This is done to supported "hybrid chat", which
172+
* injects mynah UI _after_ the connector has already been created
173+
*/
174+
export type MynahUIRef = { mynahUI: MynahUI | undefined }

packages/core/src/amazonq/webview/ui/followUps/handler.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@ import { Connector } from '../connector'
88
import { TabsStorage } from '../storages/tabsStorage'
99
import { WelcomeFollowupType } from '../apps/amazonqCommonsConnector'
1010
import { AuthFollowUpType } from './generator'
11-
import { FollowUpTypes } from '../../../commons/types'
11+
import { FollowUpTypes, MynahUIRef } from '../../../commons/types'
1212

1313
export interface FollowUpInteractionHandlerProps {
14-
mynahUI: MynahUI
14+
mynahUIRef: MynahUIRef
1515
connector: Connector
1616
tabsStorage: TabsStorage
1717
}
1818

1919
export class FollowUpInteractionHandler {
20-
private mynahUI: MynahUI
20+
private mynahUIRef: MynahUIRef
2121
private connector: Connector
2222
private tabsStorage: TabsStorage
2323

2424
constructor(props: FollowUpInteractionHandlerProps) {
25-
this.mynahUI = props.mynahUI
25+
this.mynahUIRef = props.mynahUIRef
2626
this.connector = props.connector
2727
this.tabsStorage = props.tabsStorage
2828
}
@@ -41,6 +41,11 @@ export class FollowUpInteractionHandler {
4141
this.connector.help(tabID)
4242
return
4343
}
44+
45+
if (!this.mynahUI) {
46+
return
47+
}
48+
4449
// we need to check if there is a prompt
4550
// which will cause an api call
4651
// then we can set the loading state to true
@@ -70,7 +75,7 @@ export class FollowUpInteractionHandler {
7075
}
7176

7277
const addChatItem = (tabID: string, messageId: string, options: any[]) => {
73-
this.mynahUI.addChatItem(tabID, {
78+
this.mynahUI?.addChatItem(tabID, {
7479
type: ChatItemType.ANSWER_PART,
7580
messageId,
7681
followUp: {
@@ -150,7 +155,7 @@ export class FollowUpInteractionHandler {
150155

151156
public onWelcomeFollowUpClicked(tabID: string, welcomeFollowUpType: WelcomeFollowupType) {
152157
if (welcomeFollowUpType === 'continue-to-chat') {
153-
this.mynahUI.addChatItem(tabID, {
158+
this.mynahUI?.addChatItem(tabID, {
154159
type: ChatItemType.ANSWER,
155160
body: 'Ok, please write your question below.',
156161
})
@@ -159,4 +164,8 @@ export class FollowUpInteractionHandler {
159164
return
160165
}
161166
}
167+
168+
private get mynahUI(): MynahUI | undefined {
169+
return this.mynahUIRef.mynahUI
170+
}
162171
}

0 commit comments

Comments
 (0)