Skip to content

chore: basic example how to use openTab on client/server side #865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion client/vscode/src/chatActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AUTH_FOLLOW_UP_CLICKED,
CHAT_OPTIONS,
COPY_TO_CLIPBOARD,
UiMessageResultParams,
} from '@aws/chat-client-ui-types'
import {
ChatResult,
Expand All @@ -14,13 +15,24 @@ import {
QuickActionResult,
QuickActionParams,
insertToCursorPositionNotificationType,
openTabRequestType,
} from '@aws/language-server-runtimes/protocol'
import { v4 as uuidv4 } from 'uuid'
import { Uri, ViewColumn, Webview, WebviewPanel, commands, window } from 'vscode'
import { Disposable, LanguageClient, Position, State, TextDocumentIdentifier } from 'vscode-languageclient/node'
import {
Disposable,
ErrorCodes,
LanguageClient,
Position,
ResponseError,
State,
TextDocumentIdentifier,
} from 'vscode-languageclient/node'
import * as jose from 'jose'

export function registerChat(languageClient: LanguageClient, extensionUri: Uri, encryptionKey?: Buffer) {
const openTabResponseQueue: UiMessageResultParams[] = []

const panel = window.createWebviewPanel(
'testChat', // Identifies the type of the webview. Used internally
'Chat Test', // Title of the panel displayed to the user
Expand Down Expand Up @@ -52,6 +64,35 @@ export function registerChat(languageClient: LanguageClient, extensionUri: Uri,
languageClient.info(`[VSCode Client] Received telemetry event from server ${JSON.stringify(e)}`)
})

languageClient.onRequest(openTabRequestType.method, async (params, _) => {
const mapErrorType = (type: string | undefined): number => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to re-create this function every request?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its only an example change so that I can start working on the open tab story

switch (type) {
case 'InvalidRequest':
return ErrorCodes.InvalidRequest
case 'InternalError':
return ErrorCodes.InternalError
case 'UnknownError':
default:
return ErrorCodes.UnknownErrorCode
}
}
panel.webview.postMessage({
command: openTabRequestType.method,
params: params,
})

const result = await waitForOpenTabResult()

if (result?.success) {
return { tabId: result.result.tabId }
} else {
return new ResponseError(
mapErrorType(result?.error.type),
result?.error.message ?? 'No response from client'
)
}
})

panel.webview.onDidReceiveMessage(async message => {
languageClient.info(`[VSCode Client] Received ${JSON.stringify(message)} from chat`)

Expand Down Expand Up @@ -132,12 +173,30 @@ export function registerChat(languageClient: LanguageClient, extensionUri: Uri,
if (!isValidAuthFollowUpType(message.params.followUp.type))
languageClient.sendNotification(followUpClickNotificationType, message.params)
break
case openTabRequestType.method:
openTabResponseQueue.push(message.params)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we resolve the promise here immediately instead of spinning a loop every 100ms?

break
default:
if (isServerEvent(message.command)) languageClient.sendNotification(message.command, message.params)
break
}
}, undefined)

async function waitForOpenTabResult(): Promise<UiMessageResultParams | undefined> {
const endTime = Date.now() + 60000 // 1 minute from now
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why one minute? How expensive is it to create a tab?

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
while (Date.now() < endTime) {
if (openTabResponseQueue.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are tab requests and responses guaranteed to be in order? Can we index them by requestId instead?

const result = openTabResponseQueue.shift()
return result
}

await sleep(100)
}

return undefined
}

panel.webview.html = getWebviewContent(panel.webview, extensionUri)

registerGenericCommand('aws.sample-vscode-ext-amazonq.explainCode', 'Explain', panel)
Expand Down
31 changes: 21 additions & 10 deletions server/aws-lsp-codewhisperer/src/language-server/qChatServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ export const QChatServer =
.withAmazonQServiceManager(amazonQServiceManager)

telemetryService = new TelemetryService(
amazonQServiceManager,
credentialsProvider,
telemetry,
logging,
)

const clientParams = safeGet(lsp.getClientInitializeParams(),new AmazonQServiceInitializationError(
'TelemetryService initialized before LSP connection was initialized.'))
amazonQServiceManager,
credentialsProvider,
telemetry,
logging,
)

const clientParams = safeGet(lsp.getClientInitializeParams(), new AmazonQServiceInitializationError(
'TelemetryService initialized before LSP connection was initialized.'))

telemetryService.updateUserContext(makeUserContextObject(clientParams, runtime.platform, 'CHAT'))

chatController = new ChatController(chatSessionManagementService, features, telemetryService)

await updateConfigurationHandler()
Expand Down Expand Up @@ -105,6 +105,17 @@ export const QChatServer =
return chatController.onCodeInsertToCursorPosition(params)
})

chat.onReady(_ => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the expected behavior? To create a new tab on load?

logging.log('Q Chat Client is ready')
chat.openTab({})
.then(result => {
logging.log(`Opened tab: ${result.tabId}`)
})
.catch(err => {
logging.log(`Error opening tab: ${err}`)
})
})

logging.log('Q Chat server has been initialized')

return () => {
Expand Down
Loading