diff --git a/package-lock.json b/package-lock.json index cc42d933cd..2b2c85ed48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "vscode-nls-dev": "^4.0.4" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.267", + "@aws-toolkits/telemetry": "^1.0.272", "@playwright/browser-chromium": "^1.43.1", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", @@ -5192,10 +5192,11 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.267", - "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.267.tgz", - "integrity": "sha512-qVEHuEW6WgqUafJP5oVtlaaWDtn2+6CklzqQgruqH7gxlNLBgi9pM9dpEC8xOYrHN3m1UW0LagUUgRS4ndDOyw==", + "version": "1.0.272", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.272.tgz", + "integrity": "sha512-cWmyTkiNDcDXaRjX7WdWO5FiNzXUKcrKpkYkkPgi8t+I9ZwwWwt6FKps7FjEGvFzTFx6I9XbsWM1mRrkuvxdQw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "ajv": "^6.12.6", "fs-extra": "^11.1.0", @@ -19394,7 +19395,7 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.83.0" + "vscode": "^1.68.0" } }, "packages/core/node_modules/@types/node": { diff --git a/package.json b/package.json index c4b768d96e..7adf1e4554 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.267", + "@aws-toolkits/telemetry": "^1.0.272", "@playwright/browser-chromium": "^1.43.1", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", diff --git a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts index 09963fb77a..3e5ea73b3b 100644 --- a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts @@ -111,7 +111,8 @@ export class Connector { eventId?: string, codeBlockIndex?: number, totalCodeBlocks?: number, - userIntent?: string + userIntent?: string, + codeBlockLanguage?: string ): void => { this.sendMessageToExtension({ tabID: tabID, @@ -125,6 +126,7 @@ export class Connector { codeBlockIndex, totalCodeBlocks, userIntent, + codeBlockLanguage, }) } @@ -137,7 +139,8 @@ export class Connector { eventId?: string, codeBlockIndex?: number, totalCodeBlocks?: number, - userIntent?: string + userIntent?: string, + codeBlockLanguage?: string ): void => { this.sendMessageToExtension({ tabID: tabID, @@ -151,6 +154,7 @@ export class Connector { codeBlockIndex, totalCodeBlocks, userIntent, + codeBlockLanguage, }) } @@ -295,6 +299,7 @@ export class Connector { canBeVoted: true, codeReference: messageData.codeReference, userIntent: messageData.userIntent, + codeBlockLanguage: messageData.codeBlockLanguage, } // If it is not there we will not set it @@ -328,6 +333,7 @@ export class Connector { messageId: messageData.messageID, codeReference: messageData.codeReference, userIntent: messageData.userIntent, + codeBlockLanguage: messageData.codeBlockLanguage, followUp: messageData.followUps !== undefined && messageData.followUps.length > 0 ? { diff --git a/packages/core/src/amazonq/webview/ui/connector.ts b/packages/core/src/amazonq/webview/ui/connector.ts index 3d743c04f3..3e105b31c1 100644 --- a/packages/core/src/amazonq/webview/ui/connector.ts +++ b/packages/core/src/amazonq/webview/ui/connector.ts @@ -51,6 +51,7 @@ export interface ChatPayload { export interface CWCChatItem extends ChatItem { traceId?: string userIntent?: UserIntent + codeBlockLanguage?: string } export interface ConnectorProps { @@ -242,7 +243,8 @@ export class Connector { eventId?: string, codeBlockIndex?: number, totalCodeBlocks?: number, - userIntent?: string + userIntent?: string, + codeBlockLanguage?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -255,7 +257,8 @@ export class Connector { eventId, codeBlockIndex, totalCodeBlocks, - userIntent + userIntent, + codeBlockLanguage ) break case 'featuredev': @@ -331,7 +334,8 @@ export class Connector { eventId?: string, codeBlockIndex?: number, totalCodeBlocks?: number, - userIntent?: string + userIntent?: string, + codeBlockLanguage?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -344,7 +348,8 @@ export class Connector { eventId, codeBlockIndex, totalCodeBlocks, - userIntent + userIntent, + codeBlockLanguage ) break case 'featuredev': diff --git a/packages/core/src/amazonq/webview/ui/main.ts b/packages/core/src/amazonq/webview/ui/main.ts index aa5ad615d4..0c12c528c9 100644 --- a/packages/core/src/amazonq/webview/ui/main.ts +++ b/packages/core/src/amazonq/webview/ui/main.ts @@ -38,7 +38,7 @@ export const createMynahUI = ( // eslint-disable-next-line prefer-const let connector: Connector //Store the mapping between messageId and messageUserIntent for amazonq_interactWithMessage telemetry - const messageUserIntentMap = new Map() + const responseMetadata = new Map() window.addEventListener('error', (e) => { const { error, message } = e @@ -252,8 +252,12 @@ export const createMynahUI = ( ? { type: ChatItemType.CODE_RESULT, fileList: item.fileList } : {}), }) - if (item.messageId !== undefined && item.userIntent !== undefined) { - messageUserIntentMap.set(item.messageId, item.userIntent) + if ( + item.messageId !== undefined && + item.userIntent !== undefined && + item.codeBlockLanguage !== undefined + ) { + responseMetadata.set(item.messageId, [item.userIntent, item.codeBlockLanguage]) } ideApi.postMessage({ command: 'update-chat-message-telemetry', @@ -515,7 +519,8 @@ export const createMynahUI = ( eventId, codeBlockIndex, totalCodeBlocks, - messageUserIntentMap.get(messageId) ?? undefined + responseMetadata.get(messageId)?.[0] ?? undefined, + responseMetadata.get(messageId)?.[1] ?? undefined ) }, onCodeBlockActionClicked: ( @@ -582,7 +587,8 @@ export const createMynahUI = ( eventId, codeBlockIndex, totalCodeBlocks, - messageUserIntentMap.get(messageId) ?? undefined + responseMetadata.get(messageId)?.[0] ?? undefined, + responseMetadata.get(messageId)?.[1] ?? undefined ) mynahUI.notify({ type: NotificationType.SUCCESS, diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts index c16bbf3c24..41a31d30ed 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts @@ -33,6 +33,7 @@ import { CodeScanIssue } from '../../../../codewhisperer/models/model' import { marked } from 'marked' import { JSDOM } from 'jsdom' import { LspController } from '../../../../amazonq/lsp/lspController' +import { extractCodeBlockLanguage } from '../../../../shared/markdown' export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help' @@ -87,6 +88,7 @@ export class Messenger { triggerID, messageID: '', userIntent: undefined, + codeBlockLanguage: undefined, }, tabID ) @@ -131,6 +133,7 @@ export class Messenger { let codeReference: CodeReference[] = [] let followUps: FollowUp[] = [] let relatedSuggestions: Suggestion[] = [] + let codeBlockLanguage: string = 'plaintext' if (response.generateAssistantResponseResponse === undefined) { throw new ToolkitError( @@ -182,7 +185,9 @@ export class Messenger { chatEvent.assistantResponseEvent.content.length > 0 ) { message += chatEvent.assistantResponseEvent.content - + if (codeBlockLanguage === 'plaintext') { + codeBlockLanguage = extractCodeBlockLanguage(message) + } this.dispatcher.sendChatMessage( new ChatMessage( { @@ -195,6 +200,7 @@ export class Messenger { triggerID, messageID, userIntent: triggerPayload.userIntent, + codeBlockLanguage: codeBlockLanguage, }, tabID ) @@ -272,6 +278,7 @@ export class Messenger { triggerID, messageID, userIntent: triggerPayload.userIntent, + codeBlockLanguage: codeBlockLanguage, }, tabID ) @@ -290,6 +297,7 @@ export class Messenger { triggerID, messageID, userIntent: triggerPayload.userIntent, + codeBlockLanguage: undefined, }, tabID ) @@ -307,6 +315,7 @@ export class Messenger { triggerID, messageID, userIntent: triggerPayload.userIntent, + codeBlockLanguage: undefined, }, tabID ) @@ -431,6 +440,7 @@ export class Messenger { triggerID, messageID: 'static_message_' + triggerID, userIntent: undefined, + codeBlockLanguage: undefined, }, tabID ) diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index d2f7e4ca62..4c269298fd 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -46,6 +46,7 @@ export interface InsertCodeAtCursorPosition { eventId: string codeBlockIndex: number totalCodeBlocks: number + codeBlockLanguage: string } export interface CopyCodeToClipboard { @@ -59,6 +60,7 @@ export interface CopyCodeToClipboard { eventId: string codeBlockIndex: number totalCodeBlocks: number + codeBlockLanguage: string } export interface AcceptDiff { diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index e5b49a6d59..0522ab29cd 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -203,6 +203,7 @@ export class CWCTelemetryHelper { cwsprChatCodeBlockIndex: message.codeBlockIndex, cwsprChatTotalCodeBlocks: message.totalCodeBlocks, cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), + cwsprChatProgrammingLanguage: message.codeBlockLanguage, } break case 'code_was_copied_to_clipboard': @@ -220,6 +221,7 @@ export class CWCTelemetryHelper { cwsprChatCodeBlockIndex: message.codeBlockIndex, cwsprChatTotalCodeBlocks: message.totalCodeBlocks, cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), + cwsprChatProgrammingLanguage: message.codeBlockLanguage, } break case 'accept_diff': diff --git a/packages/core/src/codewhispererChat/view/connector/connector.ts b/packages/core/src/codewhispererChat/view/connector/connector.ts index 00a8217de4..02794af5fb 100644 --- a/packages/core/src/codewhispererChat/view/connector/connector.ts +++ b/packages/core/src/codewhispererChat/view/connector/connector.ts @@ -142,6 +142,7 @@ export interface ChatMessageProps { readonly triggerID: string readonly messageID: string readonly userIntent: string | undefined + readonly codeBlockLanguage: string | undefined } export class ChatMessage extends UiMessage { @@ -155,6 +156,7 @@ export class ChatMessage extends UiMessage { readonly triggerID: string readonly messageID: string | undefined readonly userIntent: string | undefined + readonly codeBlockLanguage: string | undefined override type = 'chatMessage' constructor(props: ChatMessageProps, tabID: string) { @@ -168,6 +170,7 @@ export class ChatMessage extends UiMessage { this.triggerID = props.triggerID this.messageID = props.messageID this.userIntent = props.userIntent + this.codeBlockLanguage = props.codeBlockLanguage } } diff --git a/packages/core/src/codewhispererChat/view/messages/messageListener.ts b/packages/core/src/codewhispererChat/view/messages/messageListener.ts index abe37c019f..93c750ab01 100644 --- a/packages/core/src/codewhispererChat/view/messages/messageListener.ts +++ b/packages/core/src/codewhispererChat/view/messages/messageListener.ts @@ -165,6 +165,7 @@ export class UIMessageListener { eventId: msg.eventId, codeBlockIndex: msg.codeBlockIndex, totalCodeBlocks: msg.totalCodeBlocks, + codeBlockLanguage: msg.codeBlockLanguage, }) } @@ -196,6 +197,7 @@ export class UIMessageListener { eventId: msg.eventId, codeBlockIndex: msg.codeBlockIndex, totalCodeBlocks: msg.totalCodeBlocks, + codeBlockLanguage: msg.codeBlockLanguage, }) } diff --git a/packages/core/src/shared/markdown.ts b/packages/core/src/shared/markdown.ts new file mode 100644 index 0000000000..215e660390 --- /dev/null +++ b/packages/core/src/shared/markdown.ts @@ -0,0 +1,22 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export const extractCodeBlockLanguage = (message: string) => { + // This fulfills both the cases of unit test generation(java, python) and general use case(Non java and Non python) languages. + const codeBlockStart = message.indexOf('```') + if (codeBlockStart === -1) { + return 'plaintext' + } + + const languageStart = codeBlockStart + 3 + const languageEnd = message.indexOf('\n', languageStart) + + if (languageEnd === -1) { + return 'plaintext' + } + + const language = message.substring(languageStart, languageEnd).trim() + return language !== '' ? language : 'plaintext' +} diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index 005fc6c53b..9c3dbbdfd6 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -103,11 +103,6 @@ "type": "int", "description": "CPU used by LSP server as a percentage of all available CPUs on the system" }, - { - "name": "cwsprChatProgrammingLanguage", - "type": "string", - "description": "Programming language associated with the message" - }, { "name": "cwsprChatConversationType", "type": "string", @@ -836,57 +831,6 @@ } ] }, - { - "name": "amazonq_interactWithMessage", - "description": "When a user interacts with a message in the conversation", - "metadata": [ - { - "type": "cwsprChatConversationId" - }, - { - "type": "credentialStartUrl", - "required": false - }, - { - "type": "cwsprChatMessageId" - }, - { - "type": "cwsprChatUserIntent", - "required": false - }, - { - "type": "cwsprChatInteractionType" - }, - { - "type": "cwsprChatInteractionTarget", - "required": false - }, - { - "type": "cwsprChatAcceptedCharactersLength", - "required": false - }, - { - "type": "cwsprChatAcceptedNumberOfLines", - "required": false - }, - { - "type": "cwsprChatHasReference", - "required": false - }, - { - "type": "cwsprChatCodeBlockIndex", - "required": false - }, - { - "type": "cwsprChatTotalCodeBlocks", - "required": false - }, - { - "type": "cwsprChatHasProjectContext", - "required": false - } - ] - }, { "name": "amazonq_modifyCode", "description": "% of code modified by the user after copying/inserting code from a message", diff --git a/packages/core/src/test/shared/markdown.test.ts b/packages/core/src/test/shared/markdown.test.ts new file mode 100644 index 0000000000..f362a47692 --- /dev/null +++ b/packages/core/src/test/shared/markdown.test.ts @@ -0,0 +1,33 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { extractCodeBlockLanguage } from '../../shared/markdown' + +describe('extractCodeBlockLanguage', () => { + it('should return "plaintext" when no code block is present', () => { + const message = 'This is a message without a code block' + assert.strictEqual(extractCodeBlockLanguage(message), 'plaintext') + }) + + it('should return the language when a code block with language is present', () => { + const message = 'Here is some code:\n```javascript\nconsole.log("Hello");\n```' + assert.strictEqual(extractCodeBlockLanguage(message), 'javascript') + }) + + it('should return "plaintext" when a code block is present but no language is specified', () => { + const message = 'Here is some code:\n```\nconsole.log("Hello");\n```' + assert.strictEqual(extractCodeBlockLanguage(message), 'plaintext') + }) + + it('should handle whitespace before the language specification', () => { + const message = 'Code:\n``` typescript\nconst x: number = 5;\n```' + assert.strictEqual(extractCodeBlockLanguage(message), 'typescript') + }) + + it('should handle empty messages', () => { + assert.strictEqual(extractCodeBlockLanguage(''), 'plaintext') + }) +})