in client/vscode/src/chatActivation.ts [49:381]
export function registerChat(
languageClient: LanguageClient,
extensionUri: Uri,
encryptionKey?: Buffer,
agenticMode?: boolean
) {
const webviewInitialized: Promise<Webview> = new Promise(resolveWebview => {
const provider = {
resolveWebviewView(webviewView: WebviewView) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.joinPath(extensionUri, 'build')],
}
resolveWebview(webviewView.webview)
webviewView.webview.onDidReceiveMessage(async message => {
languageClient.info(`[VSCode Client] Received ${JSON.stringify(message)} from chat`)
switch (message.command) {
case COPY_TO_CLIPBOARD:
languageClient.info('[VSCode Client] Copy to clipboard event received')
break
case INSERT_TO_CURSOR_POSITION: {
const editor = window.activeTextEditor
let textDocument: TextDocumentIdentifier | undefined = undefined
let cursorPosition: Position | undefined = undefined
if (editor) {
cursorPosition = editor.selection.active
textDocument = { uri: editor.document.uri.toString() }
}
languageClient.sendNotification(insertToCursorPositionNotificationType, {
...message.params,
cursorPosition,
textDocument,
})
break
}
case AUTH_FOLLOW_UP_CLICKED:
languageClient.info('[VSCode Client] AuthFollowUp clicked')
break
case chatRequestType.method: {
const partialResultToken = uuidv4()
const chatDisposable = languageClient.onProgress(
chatRequestType,
partialResultToken,
partialResult =>
handlePartialResult<ChatResult>(
partialResult,
encryptionKey,
webviewView.webview,
message.params.tabId
)
)
const editor =
window.activeTextEditor ||
window.visibleTextEditors.find(editor => editor.document.languageId != 'Log')
if (editor) {
message.params.cursorPosition = [editor.selection.active]
message.params.textDocument = { uri: editor.document.uri.toString() }
}
const chatRequest = await encryptRequest<ChatParams>(message.params, encryptionKey)
const chatResult = await languageClient.sendRequest(chatRequestType, {
...chatRequest,
partialResultToken,
})
handleCompleteResult<ChatResult>(
chatResult,
encryptionKey,
webviewView.webview,
message.params.tabId,
chatDisposable
)
break
}
case quickActionRequestType.method: {
const quickActionPartialResultToken = uuidv4()
const quickActionDisposable = languageClient.onProgress(
quickActionRequestType,
quickActionPartialResultToken,
partialResult =>
handlePartialResult<QuickActionResult>(
partialResult,
encryptionKey,
webviewView.webview,
message.params.tabId
)
)
const quickActionRequest = await encryptRequest<QuickActionParams>(
message.params,
encryptionKey
)
const quickActionResult = await languageClient.sendRequest(quickActionRequestType, {
...quickActionRequest,
partialResultToken: quickActionPartialResultToken,
})
handleCompleteResult<ChatResult>(
quickActionResult,
encryptionKey,
webviewView.webview,
message.params.tabId,
quickActionDisposable
)
break
}
case listConversationsRequestType.method:
await handleRequest(
languageClient,
message.params,
webviewView,
listConversationsRequestType.method
)
break
case conversationClickRequestType.method:
await handleRequest(
languageClient,
message.params,
webviewView,
conversationClickRequestType.method
)
break
case tabBarActionRequestType.method:
await handleRequest(
languageClient,
message.params,
webviewView,
tabBarActionRequestType.method
)
break
case buttonClickRequestType.method:
await handleRequest(
languageClient,
message.params,
webviewView,
buttonClickRequestType.method
)
break
case followUpClickNotificationType.method:
if (!isValidAuthFollowUpType(message.params.followUp.type))
languageClient.sendNotification(followUpClickNotificationType, message.params)
break
default:
if (isServerEvent(message.command))
languageClient.sendNotification(message.command, message.params)
else languageClient.info(`[VSCode Client] Unhandled command: ${message.command}`)
break
}
}, undefined)
languageClient.onNotification(chatOptionsUpdateType, params => {
webviewView.webview.postMessage({
command: chatOptionsUpdateType.method,
params: params,
})
})
languageClient.onNotification(contextCommandsNotificationType, params => {
webviewView.webview.postMessage({
command: contextCommandsNotificationType.method,
params: params,
})
})
languageClient.onNotification(chatUpdateNotificationType, params => {
webviewView.webview.postMessage({
command: chatUpdateNotificationType.method,
params: params,
})
})
const registerHandlerWithResponseRouter = (command: string) => {
const handler = async (params: any, _: any) => {
const mapErrorType = (type: string | undefined): number => {
switch (type) {
case 'InvalidRequest':
return ErrorCodes.InvalidRequest
case 'InternalError':
return ErrorCodes.InternalError
case 'UnknownError':
default:
return ErrorCodes.UnknownErrorCode
}
}
const requestId = uuidv4()
webviewView.webview.postMessage({
requestId: requestId,
command: command,
params: params,
})
const responsePromise = new Promise<UiMessageResultParams | undefined>((resolve, reject) => {
const timeout = setTimeout(() => {
disposable.dispose()
reject(new Error('Request timed out'))
}, 30000)
const disposable = webviewView.webview.onDidReceiveMessage((message: any) => {
if (message.requestId === requestId) {
clearTimeout(timeout)
disposable.dispose()
resolve(message.params)
}
})
})
const result = await responsePromise
if (result?.success) {
return result.result
} else {
return new ResponseError(
mapErrorType(result?.error.type),
result?.error.message ?? 'No response from client'
)
}
}
languageClient.onRequest(command, handler)
}
registerHandlerWithResponseRouter(openTabRequestType.method)
registerHandlerWithResponseRouter(getSerializedChatRequestType.method)
webviewView.webview.html = getWebviewContent(webviewView.webview, extensionUri, !!agenticMode)
registerGenericCommand('aws.sample-vscode-ext-amazonq.explainCode', 'Explain', webviewView.webview)
registerGenericCommand('aws.sample-vscode-ext-amazonq.refactorCode', 'Refactor', webviewView.webview)
registerGenericCommand('aws.sample-vscode-ext-amazonq.fixCode', 'Fix', webviewView.webview)
registerGenericCommand('aws.sample-vscode-ext-amazonq.optimizeCode', 'Optimize', webviewView.webview)
commands.registerCommand('aws.sample-vscode-ext-amazonq.sendToPrompt', data => {
const triggerType = getCommandTriggerType(data)
const selection = getSelectedText()
webviewView.webview.postMessage({
command: 'sendToPrompt',
params: { selection: selection, triggerType },
})
})
commands.registerCommand('aws.sample-vscode-ext-amazonq.openTab', data => {
webviewView.webview.postMessage({
command: 'aws/chat/openTab',
params: {},
})
})
},
}
// Register the provider for the auxiliary bar
window.registerWebviewViewProvider('amazonq.chat', provider, {
webviewOptions: { retainContextWhenHidden: true },
})
})
// Listen for Initialize handshake from LSP server to register quick actions dynamically
languageClient.onDidChangeState(({ oldState, newState }) => {
if (oldState === State.Starting && newState === State.Running) {
languageClient.info(
'Language client received initializeResult from server:',
JSON.stringify(languageClient.initializeResult)
)
const chatOptions = languageClient.initializeResult?.awsServerCapabilities?.chatOptions
// We can only initialize the chat once the webview is initialized
webviewInitialized.then(webview => {
webview.postMessage({
command: CHAT_OPTIONS,
params: chatOptions,
})
})
}
})
languageClient.onTelemetry(e => {
languageClient.info(`[VSCode Client] Received telemetry event from server ${JSON.stringify(e)}`)
})
languageClient.onRequest(ShowSaveFileDialogRequestType.method, async (params: ShowSaveFileDialogParams) => {
// Show native Save File dialog
const filters: Record<string, string[]> = {}
const formatMappings = [
{ format: 'markdown', key: 'Markdown', extensions: ['md'] },
{ format: 'html', key: 'HTML', extensions: ['html'] },
]
params.supportedFormats?.forEach(format => {
const mapping = formatMappings.find(m => m.format === format)
if (mapping) {
filters[mapping.key] = mapping.extensions
}
})
const saveAtUri = params.defaultUri ? vscode.Uri.parse(params.defaultUri) : vscode.Uri.file('export-chat.md')
const targetUri = await vscode.window.showSaveDialog({
filters,
defaultUri: saveAtUri,
title: 'Export',
})
// Send message to Chat Client to do Conversation Export
if (!targetUri) {
return new ResponseError(LSPErrorCodes.RequestFailed, 'Export failed')
}
return {
targetUri: targetUri.toString(),
}
})
commands.registerCommand('aws.sample-vscode-ext-amazonq.sendInlineChat', async () => {
const params = getCurrentEditorParams()
languageClient.info(`Logging request for inline chat ${JSON.stringify(params)}`)
if (!params) {
languageClient.warn(`Invalid request params for inline chat`)
return
}
try {
const inlineChatRequest = await encryptRequest<InlineChatParams>(params, encryptionKey)
const response = await languageClient.sendRequest(inlineChatRequestType, inlineChatRequest)
const result: InlineChatResult = response as InlineChatResult
const decryptedMessage =
typeof result === 'string' && encryptionKey ? await decodeRequest(result, encryptionKey) : result
languageClient.info(`Logging response for inline chat ${JSON.stringify(decryptedMessage)}`)
} catch (e) {
languageClient.info(`Logging error for inline chat ${JSON.stringify(e)}`)
}
})
}