From 612353ab0f6e96e6cf517d3f4e712e3e26f31d5b Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Tue, 18 Mar 2025 16:06:59 -0700 Subject: [PATCH 01/18] refactor: implement payload size management for chat requests - Add size limits for different context types (user input, prompts, rules, files, workspace) - Implement truncation logic to preserve content around the middle of file text - Replace static contextMaxLength with workspaceChunkMaxSize for workspace content - Add debug logging for tracking payload sizes of different components - Filter out empty contexts from additionalContents and relevantTextDocuments - Update type definitions to include content type in AdditionalContentEntryAddition --- .../core/src/codewhispererChat/constants.ts | 5 +- .../controllers/chat/chatRequest/converter.ts | 121 ++++++++++++++++++ .../controllers/chat/controller.ts | 36 +++--- .../controllers/chat/model.ts | 6 +- 4 files changed, 143 insertions(+), 25 deletions(-) diff --git a/packages/core/src/codewhispererChat/constants.ts b/packages/core/src/codewhispererChat/constants.ts index 4566d14ec64..e1f01dbaccd 100644 --- a/packages/core/src/codewhispererChat/constants.ts +++ b/packages/core/src/codewhispererChat/constants.ts @@ -7,12 +7,13 @@ import fs from '../shared/fs/fs' export const promptFileExtension = '.md' +// limit for each entry of @prompt, @rules, @files and @folder export const additionalContentInnerContextLimit = 8192 export const aditionalContentNameLimit = 1024 -// temporary limit for @workspace and @file combined context length -export const contextMaxLength = 40_000 +// limit for each chunk of @workspace +export const workspaceChunkMaxSize = 40_960 export const getUserPromptsDirectory = () => { return path.join(fs.getUserHomeDir(), '.aws', 'amazonq', 'prompts') diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index 0a34463058e..cf236e20d61 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -13,6 +13,7 @@ import { } from '@amzn/codewhisperer-streaming' import { ChatTriggerType, TriggerPayload } from '../model' import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities' +import { getLogger } from '../../../../shared' const fqnNameSizeDownLimit = 1 const fqnNameSizeUpLimit = 256 @@ -38,6 +39,126 @@ const filePathSizeLimit = 4_000 const customerMessageSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { + // truncate + let remainingPayloadSize = 100_000 + // Type A context: Preserving userInput as much as possible + if (triggerPayload.message !== undefined) { + if (triggerPayload.message.length <= remainingPayloadSize) { + remainingPayloadSize -= triggerPayload.message.length + } else { + triggerPayload.message = triggerPayload.message.substring(0, remainingPayloadSize) + remainingPayloadSize = 0 + } + } + // TODO: send truncation telemetry if needed + getLogger().debug(`current request user input size: ${triggerPayload.message?.length}`) + + // Type B1(prompts) context: Preserving prompts as much as possible + let totalPromptSize = 0 + if (triggerPayload.additionalContents !== undefined) { + for (const additionalContent of triggerPayload.additionalContents) { + if (additionalContent.type === 'prompt' && additionalContent.innerContext !== undefined) { + if (additionalContent.innerContext.length <= remainingPayloadSize) { + remainingPayloadSize -= additionalContent.innerContext.length + } else { + additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) + remainingPayloadSize = 0 + } + totalPromptSize += additionalContent.innerContext.length + } + } + } + + getLogger().debug(`current request total prompts size: ${totalPromptSize}`) + + // Type C context: Preserving current file context as much as possible + // truncate the text to keep texts in the middle instead of blindly truncating the tail + if (triggerPayload.fileText !== undefined) { + if (triggerPayload.fileText.length <= remainingPayloadSize) { + remainingPayloadSize -= triggerPayload.fileText.length + } else { + // Calculate the middle point + const middle = Math.floor(triggerPayload.fileText.length / 2) + // Calculate how much text we can take from each side of the middle + const halfRemaining = Math.floor(remainingPayloadSize / 2) + // Get text from around the middle point + const startPos = middle - halfRemaining + const endPos = middle + halfRemaining + + triggerPayload.fileText = triggerPayload.fileText.substring(startPos, endPos) + remainingPayloadSize = 0 + } + } + getLogger().debug(`current request file content size: ${triggerPayload.fileText?.length}`) + + // Type B1(rules) context: Preserving rules as much as possible + let totalRulesSize = 0 + if (triggerPayload.additionalContents !== undefined) { + for (const additionalContent of triggerPayload.additionalContents) { + if (additionalContent.type === 'rule' && additionalContent.innerContext !== undefined) { + if (additionalContent.innerContext.length <= remainingPayloadSize) { + remainingPayloadSize -= additionalContent.innerContext.length + } else { + additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) + remainingPayloadSize = 0 + } + totalRulesSize += additionalContent.innerContext.length + } + } + } + + getLogger().debug(`current request rules size: ${totalRulesSize}`) + + // Type B2(explicit @files) context: Preserving files as much as possible + if (triggerPayload.additionalContents !== undefined) { + for (const additionalContent of triggerPayload.additionalContents) { + if (additionalContent.type === 'file' && additionalContent.innerContext !== undefined) { + if (additionalContent.innerContext.length <= remainingPayloadSize) { + remainingPayloadSize -= additionalContent.innerContext.length + } else { + additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) + remainingPayloadSize = 0 + } + } + } + } + + // Type B3 @workspace context: Preserving workspace as much as possible + let totalWorkspaceSize = 0 + if (triggerPayload.relevantTextDocuments !== undefined) { + for (const relevantDocument of triggerPayload.relevantTextDocuments) { + if (relevantDocument.text !== undefined) { + if (relevantDocument.text.length <= remainingPayloadSize) { + // remainingPayloadSize -= relevantDocument.text.length + } else { + // relevantDocument.text = relevantDocument.text.substring(0, remainingPayloadSize) + // remainingPayloadSize = 0 + } + totalWorkspaceSize += relevantDocument.text.length + } + } + } + + getLogger().debug(`current request workspace size: ${totalWorkspaceSize}`) + + getLogger().debug( + `current request total payload size: ${(triggerPayload.message?.length ?? 0) + totalPromptSize + (triggerPayload.fileText?.length ?? 0) + totalRulesSize + totalWorkspaceSize}` + ) + + // Filter out empty innerContext from additionalContents + if (triggerPayload.additionalContents !== undefined) { + triggerPayload.additionalContents = triggerPayload.additionalContents.filter( + (content) => content.innerContext !== undefined && content.innerContext !== '' + ) + } + + // Filter out empty text from relevantTextDocuments + if (triggerPayload.relevantTextDocuments !== undefined) { + triggerPayload.relevantTextDocuments = triggerPayload.relevantTextDocuments.filter( + (doc) => doc.text !== undefined && doc.text !== '' + ) + } + let document: TextDocument | undefined = undefined let cursorState: CursorState | undefined = undefined diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 846f3c6e445..4e832f43f63 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -77,7 +77,7 @@ import { createSavedPromptCommandId, aditionalContentNameLimit, additionalContentInnerContextLimit, - contextMaxLength, + workspaceChunkMaxSize, } from '../../constants' import { ChatSession } from '../../clients/chat/v0/chat' @@ -967,7 +967,6 @@ export class ChatController { getLogger().verbose(`Could not get context command prompts: ${e}`) } - let currentContextLength = 0 triggerPayload.additionalContents = [] const emptyLengths = { fileContextLength: 0, @@ -990,15 +989,16 @@ export class ChatController { name: prompt.name.substring(0, aditionalContentNameLimit), description: description.substring(0, aditionalContentNameLimit), innerContext: prompt.content.substring(0, additionalContentInnerContextLimit), + type: contextType, } // make sure the relevantDocument + additionalContext // combined does not exceed 40k characters before generating the request payload. // Do truncation and make sure triggerPayload.documentReferences is up-to-date after truncation // TODO: Use a context length indicator - if (currentContextLength + entry.innerContext.length > contextMaxLength) { - getLogger().warn(`Selected context exceeds context size limit: ${entry.description} `) - break - } + // if (currentContextLength + entry.innerContext.length > contextMaxLength) { + // getLogger().warn(`Selected context exceeds context size limit: ${entry.description} `) + // break + // } if (contextType === 'rule') { triggerPayload.truncatedAdditionalContextLengths.ruleContextLength += entry.innerContext.length @@ -1009,7 +1009,6 @@ export class ChatController { } triggerPayload.additionalContents.push(entry) - currentContextLength += entry.innerContext.length let relativePath = path.relative(workspaceFolder, prompt.filePath) // Handle user prompts outside the workspace if (prompt.filePath.startsWith(getUserPromptsDirectory())) { @@ -1067,25 +1066,20 @@ export class ChatController { triggerPayload.message = triggerPayload.message.replace(/@workspace/, '') if (CodeWhispererSettings.instance.isLocalIndexEnabled()) { const start = performance.now() - let remainingContextLength = contextMaxLength - for (const additionalContent of triggerPayload.additionalContents || []) { - if (additionalContent.innerContext) { - remainingContextLength -= additionalContent.innerContext.length - } - } + // for (const additionalContent of triggerPayload.additionalContents || []) { + // if (additionalContent.innerContext) { + // remainingContextLength -= additionalContent.innerContext.length + // } + // } triggerPayload.relevantTextDocuments = [] const relevantTextDocuments = await LspController.instance.query(triggerPayload.message) for (const relevantDocument of relevantTextDocuments) { if (relevantDocument.text !== undefined && relevantDocument.text.length > 0) { - if (remainingContextLength > relevantDocument.text.length) { - triggerPayload.relevantTextDocuments.push(relevantDocument) - remainingContextLength -= relevantDocument.text.length - } else { - getLogger().warn( - `Retrieved context exceeds context size limit: ${relevantDocument.relativeFilePath} ` - ) - break + if (relevantDocument.text.length > workspaceChunkMaxSize) { + relevantDocument.text = relevantDocument.text.substring(0, workspaceChunkMaxSize) + getLogger().debug(`Truncating @workspace chunk: ${relevantDocument.relativeFilePath} `) } + triggerPayload.relevantTextDocuments.push(relevantDocument) } } diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index ae0c6b61063..80018a89a26 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -178,7 +178,7 @@ export interface TriggerPayload { readonly query: string | undefined readonly codeSelection: Selection | undefined readonly trigger: ChatTriggerType - readonly fileText: string | undefined + fileText: string | undefined readonly fileLanguage: string | undefined readonly filePath: string | undefined message: string | undefined @@ -188,7 +188,7 @@ export interface TriggerPayload { readonly customization: Customization readonly context?: string[] | QuickActionCommand[] relevantTextDocuments?: RelevantTextDocumentAddition[] - additionalContents?: AdditionalContentEntry[] + additionalContents?: AdditionalContentEntryAddition[] // a reference to all documents used in chat payload // for providing better context transparency documentReferences?: DocumentReference[] @@ -216,6 +216,8 @@ export type AdditionalContextInfo = { // TODO move this to API definition (or just use this across the codebase) export type RelevantTextDocumentAddition = RelevantTextDocument & { startLine: number; endLine: number } +export type AdditionalContentEntryAddition = AdditionalContentEntry & { type: string } + export interface DocumentReference { readonly relativeFilePath: string readonly lineRanges: Array<{ first: number; second: number }> From cd31034e79315e7e655c7030c427148b6606638d Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 20 Mar 2025 01:03:26 -0700 Subject: [PATCH 02/18] fix linter --- .../codewhispererChat/controllers/chat/chatRequest/converter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index cf236e20d61..4cb44273455 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -13,7 +13,7 @@ import { } from '@amzn/codewhisperer-streaming' import { ChatTriggerType, TriggerPayload } from '../model' import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities' -import { getLogger } from '../../../../shared' +import { getLogger } from '../../../../shared/logger/logger' const fqnNameSizeDownLimit = 1 const fqnNameSizeUpLimit = 256 From 15dbcc0e167a231cbbd4c1e440edd882f8a62a41 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 20 Mar 2025 16:18:24 -0700 Subject: [PATCH 03/18] refactor --- .../controllers/chat/chatRequest/converter.ts | 271 ++++++++++++------ .../controllers/chat/controller.ts | 61 ++-- .../controllers/chat/model.ts | 2 +- 3 files changed, 205 insertions(+), 129 deletions(-) diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index 4cb44273455..38ed9756f15 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -11,7 +11,7 @@ import { SymbolType, TextDocument, } from '@amzn/codewhisperer-streaming' -import { ChatTriggerType, TriggerPayload } from '../model' +import { AdditionalContentEntryAddition, ChatTriggerType, RelevantTextDocumentAddition, TriggerPayload } from '../model' import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities' import { getLogger } from '../../../../shared/logger/logger' @@ -39,110 +39,47 @@ const filePathSizeLimit = 4_000 const customerMessageSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { - // truncate + // Flexible truncation logic let remainingPayloadSize = 100_000 - // Type A context: Preserving userInput as much as possible - if (triggerPayload.message !== undefined) { - if (triggerPayload.message.length <= remainingPayloadSize) { - remainingPayloadSize -= triggerPayload.message.length - } else { - triggerPayload.message = triggerPayload.message.substring(0, remainingPayloadSize) - remainingPayloadSize = 0 - } - } - // TODO: send truncation telemetry if needed - getLogger().debug(`current request user input size: ${triggerPayload.message?.length}`) - - // Type B1(prompts) context: Preserving prompts as much as possible - let totalPromptSize = 0 - if (triggerPayload.additionalContents !== undefined) { - for (const additionalContent of triggerPayload.additionalContents) { - if (additionalContent.type === 'prompt' && additionalContent.innerContext !== undefined) { - if (additionalContent.innerContext.length <= remainingPayloadSize) { - remainingPayloadSize -= additionalContent.innerContext.length - } else { - additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) - remainingPayloadSize = 0 - } - totalPromptSize += additionalContent.innerContext.length - } - } - } + const userInputTruncationInfo = preserveContexts(triggerPayload, remainingPayloadSize, ChatContextType.UserInput) - getLogger().debug(`current request total prompts size: ${totalPromptSize}`) + // Type B1(prompts) context: Preserving @prompt as much as possible + const userSpecificPromptsTruncationInfo = preserveContexts( + triggerPayload, + userInputTruncationInfo.remainingPayloadSize, + ChatContextType.UserSpecificPrompts + ) // Type C context: Preserving current file context as much as possible - // truncate the text to keep texts in the middle instead of blindly truncating the tail - if (triggerPayload.fileText !== undefined) { - if (triggerPayload.fileText.length <= remainingPayloadSize) { - remainingPayloadSize -= triggerPayload.fileText.length - } else { - // Calculate the middle point - const middle = Math.floor(triggerPayload.fileText.length / 2) - // Calculate how much text we can take from each side of the middle - const halfRemaining = Math.floor(remainingPayloadSize / 2) - // Get text from around the middle point - const startPos = middle - halfRemaining - const endPos = middle + halfRemaining - - triggerPayload.fileText = triggerPayload.fileText.substring(startPos, endPos) - remainingPayloadSize = 0 - } - } - getLogger().debug(`current request file content size: ${triggerPayload.fileText?.length}`) + const currentFileTruncationInfo = preserveContexts( + triggerPayload, + userSpecificPromptsTruncationInfo.remainingPayloadSize, + ChatContextType.CurrentFile + ) // Type B1(rules) context: Preserving rules as much as possible - let totalRulesSize = 0 - if (triggerPayload.additionalContents !== undefined) { - for (const additionalContent of triggerPayload.additionalContents) { - if (additionalContent.type === 'rule' && additionalContent.innerContext !== undefined) { - if (additionalContent.innerContext.length <= remainingPayloadSize) { - remainingPayloadSize -= additionalContent.innerContext.length - } else { - additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) - remainingPayloadSize = 0 - } - totalRulesSize += additionalContent.innerContext.length - } - } - } - - getLogger().debug(`current request rules size: ${totalRulesSize}`) + const userSpecificRulesTruncationInfo = preserveContexts( + triggerPayload, + currentFileTruncationInfo.remainingPayloadSize, + ChatContextType.UserSpecificRules + ) // Type B2(explicit @files) context: Preserving files as much as possible - if (triggerPayload.additionalContents !== undefined) { - for (const additionalContent of triggerPayload.additionalContents) { - if (additionalContent.type === 'file' && additionalContent.innerContext !== undefined) { - if (additionalContent.innerContext.length <= remainingPayloadSize) { - remainingPayloadSize -= additionalContent.innerContext.length - } else { - additionalContent.innerContext = additionalContent.innerContext.substring(0, remainingPayloadSize) - remainingPayloadSize = 0 - } - } - } - } + const userSpecificFilesTruncationInfo = preserveContexts( + triggerPayload, + userSpecificRulesTruncationInfo.remainingPayloadSize, + ChatContextType.UserSpecificFiles + ) // Type B3 @workspace context: Preserving workspace as much as possible - let totalWorkspaceSize = 0 - if (triggerPayload.relevantTextDocuments !== undefined) { - for (const relevantDocument of triggerPayload.relevantTextDocuments) { - if (relevantDocument.text !== undefined) { - if (relevantDocument.text.length <= remainingPayloadSize) { - // remainingPayloadSize -= relevantDocument.text.length - } else { - // relevantDocument.text = relevantDocument.text.substring(0, remainingPayloadSize) - // remainingPayloadSize = 0 - } - totalWorkspaceSize += relevantDocument.text.length - } - } - } - - getLogger().debug(`current request workspace size: ${totalWorkspaceSize}`) + const workspaceTruncationInfo = preserveContexts( + triggerPayload, + userSpecificFilesTruncationInfo.remainingPayloadSize, + ChatContextType.Workspace + ) getLogger().debug( - `current request total payload size: ${(triggerPayload.message?.length ?? 0) + totalPromptSize + (triggerPayload.fileText?.length ?? 0) + totalRulesSize + totalWorkspaceSize}` + `current request total payload size: ${userInputTruncationInfo.sizeAfter + userSpecificFilesTruncationInfo.sizeAfter + currentFileTruncationInfo.sizeAfter + userSpecificFilesTruncationInfo.sizeAfter + workspaceTruncationInfo.sizeAfter}` ) // Filter out empty innerContext from additionalContents @@ -247,3 +184,151 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c }, } } + +function preserveContexts( + triggerPayload: TriggerPayload, + remainingPayloadSize: number, + contextType: ChatContextType +): FlexibleTruncationInfo { + const typeToContextMap = new Map< + ChatContextType, + string | AdditionalContentEntryAddition[] | RelevantTextDocumentAddition[] | undefined + >([ + [ChatContextType.UserInput, triggerPayload.message], + [ChatContextType.CurrentFile, triggerPayload.fileText], + [ChatContextType.UserSpecificPrompts, triggerPayload.additionalContents], + [ChatContextType.UserSpecificRules, triggerPayload.additionalContents], + [ChatContextType.UserSpecificFiles, triggerPayload.additionalContents], + [ChatContextType.Workspace, triggerPayload.relevantTextDocuments], + ]) + + let truncationInfo = { + remainingPayloadSize: remainingPayloadSize, + sizeBefore: 0, + sizeAfter: 0, + textAfter: '', + } + + const contexts = typeToContextMap.get(contextType) + if (!contexts) { + getLogger().debug( + `Current request context size: type: ${contextType}, before: ${truncationInfo.sizeBefore}, after: ${truncationInfo.sizeAfter}` + ) + return truncationInfo + } + + switch (contextType) { + case ChatContextType.UserInput: + truncationInfo = truncate(contexts as string, truncationInfo) + triggerPayload.message = truncationInfo.textAfter + break + case ChatContextType.CurrentFile: + truncationInfo = truncate(contexts as string, truncationInfo) + triggerPayload.fileText = truncationInfo.textAfter + break + case ChatContextType.UserSpecificPrompts: + truncationInfo = truncateUserSpecificContexts( + contexts as AdditionalContentEntryAddition[], + truncationInfo, + 'prompt' + ) + break + case ChatContextType.UserSpecificRules: + truncationInfo = truncateUserSpecificContexts( + contexts as AdditionalContentEntryAddition[], + truncationInfo, + 'rule' + ) + break + case ChatContextType.UserSpecificFiles: + truncationInfo = truncateUserSpecificContexts( + contexts as AdditionalContentEntryAddition[], + truncationInfo, + 'file' + ) + break + case ChatContextType.Workspace: + truncationInfo = truncateWorkspaceContexts(contexts as RelevantTextDocumentAddition[], truncationInfo) + break + default: + getLogger().warn(`Unexpected context type: ${contextType}`) + return truncationInfo + } + + getLogger().debug( + `Current request context size: type: ${contextType}, before: ${truncationInfo.sizeBefore}, after: ${truncationInfo.sizeAfter}` + ) + return truncationInfo +} + +function truncateUserSpecificContexts( + contexts: AdditionalContentEntryAddition[], + truncationInfo: FlexibleTruncationInfo, + type: string +): FlexibleTruncationInfo { + for (const context of contexts) { + if (context.type !== type || !context.innerContext) { + continue + } + truncationInfo = truncate(context.innerContext, truncationInfo) + context.innerContext = truncationInfo.textAfter + } + return truncationInfo +} + +function truncateWorkspaceContexts( + contexts: RelevantTextDocumentAddition[], + truncationInfo: FlexibleTruncationInfo +): FlexibleTruncationInfo { + for (const context of contexts) { + if (!context.text) { + continue + } + truncationInfo = truncate(context.text, truncationInfo) + context.text = truncationInfo.textAfter + } + return truncationInfo +} + +function truncate( + textBefore: string, + truncationInfo: FlexibleTruncationInfo, + isCurrentFile: Boolean = false +): FlexibleTruncationInfo { + const sizeBefore = truncationInfo.sizeBefore + textBefore.length + + // for all other types of contexts, we simply truncate the tail, + // for current file context, since it's expanded from the middle context, we truncate head and tail to preserve middle context + const middle = Math.floor(textBefore.length / 2) + const halfRemaining = Math.floor(truncationInfo.remainingPayloadSize / 2) + const startPos = isCurrentFile ? middle - halfRemaining : 0 + const endPos = isCurrentFile + ? middle + (truncationInfo.remainingPayloadSize - halfRemaining) + : Math.min(textBefore.length, truncationInfo.remainingPayloadSize) + const textAfter = textBefore.substring(startPos, endPos) + + const sizeAfter = truncationInfo.sizeAfter + textAfter.length + const remainingPayloadSize = truncationInfo.remainingPayloadSize - textAfter.length + return { + remainingPayloadSize, + sizeBefore, + sizeAfter, + textAfter, + } +} + +type FlexibleTruncationInfo = { + readonly remainingPayloadSize: number + readonly sizeBefore: number + readonly sizeAfter: number + readonly textAfter: string +} + +export enum ChatContextType { + UserInput = 'userInput', + CurrentFile = 'currentFile', + UserSpecificPrompts = 'userSpecificPrompts', + UserSpecificRules = 'userSpecificRules', + UserSpecificFiles = 'userSpecificFiles', + Workspace = 'workspace', +} diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 4e832f43f63..9aee87d0f5f 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -907,12 +907,8 @@ export class ChatController { return rulesFiles } - private async resolveContextCommandPayload( - triggerPayload: TriggerPayload, - session: ChatSession - ): Promise { + private async resolveContextCommandPayload(triggerPayload: TriggerPayload, session: ChatSession) { const contextCommands: ContextCommandItem[] = [] - const relativePaths: string[] = [] // Check for workspace rules to add to context const workspaceRules = await this.collectWorkspaceRules() @@ -985,20 +981,19 @@ export class ChatController { contextType === 'rule' || contextType === 'prompt' ? `You must follow the instructions in ${prompt.relativePath}. Below are lines ${prompt.startLine}-${prompt.endLine} of this file:\n` : prompt.description + + // Handle user prompts outside the workspace + const relativePath = prompt.filePath.startsWith(getUserPromptsDirectory()) + ? path.basename(prompt.filePath) + : path.relative(workspaceFolder, prompt.filePath) + const entry = { name: prompt.name.substring(0, aditionalContentNameLimit), description: description.substring(0, aditionalContentNameLimit), innerContext: prompt.content.substring(0, additionalContentInnerContextLimit), type: contextType, + relativePath: relativePath, } - // make sure the relevantDocument + additionalContext - // combined does not exceed 40k characters before generating the request payload. - // Do truncation and make sure triggerPayload.documentReferences is up-to-date after truncation - // TODO: Use a context length indicator - // if (currentContextLength + entry.innerContext.length > contextMaxLength) { - // getLogger().warn(`Selected context exceeds context size limit: ${entry.description} `) - // break - // } if (contextType === 'rule') { triggerPayload.truncatedAdditionalContextLengths.ruleContextLength += entry.innerContext.length @@ -1009,17 +1004,9 @@ export class ChatController { } triggerPayload.additionalContents.push(entry) - let relativePath = path.relative(workspaceFolder, prompt.filePath) - // Handle user prompts outside the workspace - if (prompt.filePath.startsWith(getUserPromptsDirectory())) { - relativePath = path.basename(prompt.filePath) - } - relativePaths.push(relativePath) } } getLogger().info(`Retrieved chunks of additional context count: ${triggerPayload.additionalContents.length} `) - - return relativePaths } private async generateResponse( @@ -1056,7 +1043,7 @@ export class ChatController { } const session = this.sessionStorage.getSession(tabID) - const relativePathsOfContextCommandFiles = await this.resolveContextCommandPayload(triggerPayload, session) + await this.resolveContextCommandPayload(triggerPayload, session) triggerPayload.useRelevantDocuments = triggerPayload.context?.some( (context) => typeof context !== 'string' && context.command === '@workspace' @@ -1095,28 +1082,32 @@ export class ChatController { } } - triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments || []) - const request = triggerPayloadToChatRequest(triggerPayload) + triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments || []) + // Update context transparency after it's truncated dynamically to show users only the context sent. if (triggerPayload.documentReferences !== undefined) { const relativePathsOfMergedRelevantDocuments = triggerPayload.documentReferences.map( (doc) => doc.relativeFilePath ) const seen: string[] = [] - for (const relativePath of relativePathsOfContextCommandFiles) { - if (!relativePathsOfMergedRelevantDocuments.includes(relativePath) && !seen.includes(relativePath)) { - triggerPayload.documentReferences.push({ - relativeFilePath: relativePath, - lineRanges: [{ first: -1, second: -1 }], - }) - seen.push(relativePath) + if (triggerPayload.additionalContents) { + for (const additionalContent of triggerPayload.additionalContents) { + const relativePath = additionalContent.relativePath + if ( + !relativePathsOfMergedRelevantDocuments.includes(relativePath) && + !seen.includes(relativePath) + ) { + triggerPayload.documentReferences.push({ + relativeFilePath: relativePath, + lineRanges: [{ first: -1, second: -1 }], + }) + seen.push(relativePath) + } } } - if (triggerPayload.documentReferences) { - for (const doc of triggerPayload.documentReferences) { - session.contexts.set(doc.relativeFilePath, doc.lineRanges) - } + for (const doc of triggerPayload.documentReferences) { + session.contexts.set(doc.relativeFilePath, doc.lineRanges) } } diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index 80018a89a26..4a68040660e 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -216,7 +216,7 @@ export type AdditionalContextInfo = { // TODO move this to API definition (or just use this across the codebase) export type RelevantTextDocumentAddition = RelevantTextDocument & { startLine: number; endLine: number } -export type AdditionalContentEntryAddition = AdditionalContentEntry & { type: string } +export type AdditionalContentEntryAddition = AdditionalContentEntry & { type: string; relativePath: string } export interface DocumentReference { readonly relativeFilePath: string From 09c318ca3cd738bbc14f7a46a11a26f054073a72 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 20 Mar 2025 16:31:48 -0700 Subject: [PATCH 04/18] add missing comment --- .../codewhispererChat/controllers/chat/chatRequest/converter.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index 38ed9756f15..77cff0455a0 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -41,6 +41,8 @@ const customerMessageSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { // Flexible truncation logic let remainingPayloadSize = 100_000 + + // Type A context: Preserving user input as much as possible const userInputTruncationInfo = preserveContexts(triggerPayload, remainingPayloadSize, ChatContextType.UserInput) // Type B1(prompts) context: Preserving @prompt as much as possible From 8a22814589a455b74e767af6ae6a325fde9cae23 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Thu, 20 Mar 2025 18:22:43 -0700 Subject: [PATCH 05/18] lint --- .../controllers/chat/chatRequest/converter.ts | 2 +- .../src/codewhispererChat/controllers/chat/controller.ts | 5 ----- .../codewhispererChat/controllers/chat/telemetryHelper.ts | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index 77cff0455a0..ea6bc79d9a1 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -40,7 +40,7 @@ const customerMessageSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { // Flexible truncation logic - let remainingPayloadSize = 100_000 + const remainingPayloadSize = 100_000 // Type A context: Preserving user input as much as possible const userInputTruncationInfo = preserveContexts(triggerPayload, remainingPayloadSize, ChatContextType.UserInput) diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 9aee87d0f5f..7166b9122b2 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -1053,11 +1053,6 @@ export class ChatController { triggerPayload.message = triggerPayload.message.replace(/@workspace/, '') if (CodeWhispererSettings.instance.isLocalIndexEnabled()) { const start = performance.now() - // for (const additionalContent of triggerPayload.additionalContents || []) { - // if (additionalContent.innerContext) { - // remainingContextLength -= additionalContent.innerContext.length - // } - // } triggerPayload.relevantTextDocuments = [] const relevantTextDocuments = await LspController.instance.query(triggerPayload.message) for (const relevantDocument of relevantTextDocuments) { diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index eb99b8c5dca..2496caff07b 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -529,7 +529,7 @@ export class CWCTelemetryHelper { credentialStartUrl: AuthUtil.instance.startUrl, codewhispererCustomizationArn: triggerPayload.customization.arn, cwsprChatHasProjectContext: hasProjectLevelContext, - cwsprChatHasContextList: triggerPayload.documentReferences && triggerPayload.documentReferences?.length > 0, + cwsprChatHasContextList: triggerPayload.documentReferences && triggerPayload.documentReferences.length > 0, cwsprChatFolderContextCount: contextCounts.folderContextCount, cwsprChatFileContextCount: contextCounts.fileContextCount, cwsprChatFileContextLength: triggerPayload.additionalContextLengths?.fileContextLength ?? 0, From 39e6b16d15ddcd423dc7737970189223bcea2fba Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 03:42:54 -0700 Subject: [PATCH 06/18] Make many fields in triggerPayload not undefined, so that we don't need many undefined checks becaus we are treating them as 0 in the end anyway --- .../inlineChat/provider/inlineChatProvider.ts | 32 ++- .../amazonqTest/chat/controller/controller.ts | 17 +- .../core/src/codewhispererChat/constants.ts | 20 ++ .../controllers/chat/chatRequest/converter.ts | 48 ++-- .../controllers/chat/controller.ts | 173 +++++++-------- .../controllers/chat/model.ts | 28 ++- .../controllers/chat/telemetryHelper.ts | 24 +- .../src/shared/telemetry/vscodeTelemetry.json | 207 ++++++++++++++++++ 8 files changed, 406 insertions(+), 143 deletions(-) diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index aad41b3cd1b..cc8eed71162 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -55,17 +55,40 @@ export class InlineChatProvider { }) return this.generateResponse( { - message: message.message, + message: message.message ?? '', trigger: ChatTriggerType.InlineChatMessage, query: message.message, codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: context?.focusAreaContext?.extendedCodeBlock, + fileText: context?.focusAreaContext?.extendedCodeBlock ?? '', fileLanguage: context?.activeFileContext?.fileLanguage, filePath: context?.activeFileContext?.filePath, matchPolicy: context?.activeFileContext?.matchPolicy, codeQuery: context?.focusAreaContext?.names, userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message), customization: getSelectedCustomization(), + context: [], + relevantTextDocuments: [], + additionalContents: [], + documentReferences: [], + useRelevantDocuments: false, + contextLengths: { + additionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + truncatedAdditionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + workspaceContextLength: 0, + truncatedWorkspaceContextLength: 0, + userInputContextLength: 0, + truncatedUserInputContextLength: 0, + currentFileContextLength: 0, + truncatedCurrentFileContextLength: 0, + }, }, triggerID ) @@ -111,7 +134,10 @@ export class InlineChatProvider { const request = triggerPayloadToChatRequest(triggerPayload) const session = this.sessionStorage.getSession(tabID) - getLogger().info(`request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: %O`, request) + getLogger().debug( + `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: %O`, + request + ) let response: GenerateAssistantResponseCommandOutput | undefined = undefined session.createNewTokenSource() diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts index 4e29bb31464..cb5cbc2e851 100644 --- a/packages/core/src/amazonqTest/chat/controller/controller.ts +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -36,7 +36,11 @@ import { import { UserIntent } from '@amzn/codewhisperer-streaming' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' -import { ChatItemVotedMessage, ChatTriggerType } from '../../../codewhispererChat/controllers/chat/model' +import { + ChatItemVotedMessage, + ChatTriggerType, + TriggerPayload, +} from '../../../codewhispererChat/controllers/chat/model' import { triggerPayloadToChatRequest } from '../../../codewhispererChat/controllers/chat/chatRequest/converter' import { EditorContentController } from '../../../amazonq/commons/controllers/contentController' import { amazonQTabSuffix } from '../../../shared/constants' @@ -67,6 +71,7 @@ import { TargetFileInfo } from '../../../codewhisperer/client/codewhispereruserc import { submitFeedback } from '../../../feedback/vue/submitFeedback' import { placeholder } from '../../../shared/vscode/commands2' import { Auth } from '../../../auth/auth' +import { defaultContextLengths } from '../../../codewhispererChat/constants' export interface TestChatControllerEventEmitters { readonly tabOpened: vscode.EventEmitter @@ -916,7 +921,7 @@ export class TestController { // TODO: Write this entire gen response to basiccommands and call here. const editorText = await fs.readFileText(filePath) - const triggerPayload = { + const triggerPayload: TriggerPayload = { query: `Generate unit tests for the following part of my code: ${message?.trim() || fileName}`, codeSelection: undefined, trigger: ChatTriggerType.ChatMessage, @@ -928,6 +933,14 @@ export class TestController { codeQuery: undefined, userIntent: UserIntent.GENERATE_UNIT_TESTS, customization: getSelectedCustomization(), + context: [], + relevantTextDocuments: [], + additionalContents: [], + documentReferences: [], + useRelevantDocuments: false, + contextLengths: { + ...defaultContextLengths, + }, } const chatRequest = triggerPayloadToChatRequest(triggerPayload) const client = await createCodeWhispererChatStreamingClient() diff --git a/packages/core/src/codewhispererChat/constants.ts b/packages/core/src/codewhispererChat/constants.ts index e1f01dbaccd..bacc037007b 100644 --- a/packages/core/src/codewhispererChat/constants.ts +++ b/packages/core/src/codewhispererChat/constants.ts @@ -4,6 +4,7 @@ */ import * as path from 'path' import fs from '../shared/fs/fs' +import { ContextLengths } from './controllers/chat/model' export const promptFileExtension = '.md' @@ -20,3 +21,22 @@ export const getUserPromptsDirectory = () => { } export const createSavedPromptCommandId = 'create-saved-prompt' + +export const defaultContextLengths: ContextLengths = { + additionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + truncatedAdditionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + workspaceContextLength: 0, + truncatedWorkspaceContextLength: 0, + userInputContextLength: 0, + truncatedUserInputContextLength: 0, + currentFileContextLength: 0, + truncatedCurrentFileContextLength: 0, +} diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index ea6bc79d9a1..b098198bd46 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -3,14 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - ConversationState, - CursorState, - DocumentSymbol, - RelevantTextDocument, - SymbolType, - TextDocument, -} from '@amzn/codewhisperer-streaming' +import { ConversationState, CursorState, DocumentSymbol, SymbolType, TextDocument } from '@amzn/codewhisperer-streaming' import { AdditionalContentEntryAddition, ChatTriggerType, RelevantTextDocumentAddition, TriggerPayload } from '../model' import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities' import { getLogger } from '../../../../shared/logger/logger' @@ -36,7 +29,6 @@ export const supportedLanguagesList = [ ] const filePathSizeLimit = 4_000 -const customerMessageSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { // Flexible truncation logic @@ -81,7 +73,7 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c ) getLogger().debug( - `current request total payload size: ${userInputTruncationInfo.sizeAfter + userSpecificFilesTruncationInfo.sizeAfter + currentFileTruncationInfo.sizeAfter + userSpecificFilesTruncationInfo.sizeAfter + workspaceTruncationInfo.sizeAfter}` + `current request total payload size: ${userInputTruncationInfo.sizeAfter + currentFileTruncationInfo.sizeAfter + userSpecificRulesTruncationInfo.sizeAfter + userSpecificFilesTruncationInfo.sizeAfter + workspaceTruncationInfo.sizeAfter}` ) // Filter out empty innerContext from additionalContents @@ -92,11 +84,9 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c } // Filter out empty text from relevantTextDocuments - if (triggerPayload.relevantTextDocuments !== undefined) { - triggerPayload.relevantTextDocuments = triggerPayload.relevantTextDocuments.filter( - (doc) => doc.text !== undefined && doc.text !== '' - ) - } + triggerPayload.relevantTextDocuments = triggerPayload.relevantTextDocuments.filter( + (doc) => doc.text !== undefined && doc.text !== '' + ) let document: TextDocument | undefined = undefined let cursorState: CursorState | undefined = undefined @@ -154,10 +144,6 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c } } - const relevantDocuments: RelevantTextDocument[] = triggerPayload.relevantTextDocuments - ? triggerPayload.relevantTextDocuments - : [] - const useRelevantDocuments = triggerPayload.useRelevantDocuments // service will throw validation exception if string is empty const customizationArn: string | undefined = undefinedIfEmpty(triggerPayload.customization.arn) const chatTriggerType = triggerPayload.trigger === ChatTriggerType.InlineChatMessage ? 'INLINE_CHAT' : 'MANUAL' @@ -166,15 +152,13 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c conversationState: { currentMessage: { userInputMessage: { - content: triggerPayload.message - ? triggerPayload.message.substring(0, customerMessageSizeLimit) - : '', + content: triggerPayload.message, userInputMessageContext: { editorState: { document, cursorState, - relevantDocuments, - useRelevantDocuments, + relevantDocuments: triggerPayload.relevantTextDocuments, + useRelevantDocuments: triggerPayload.useRelevantDocuments, }, additionalContext: triggerPayload.additionalContents, }, @@ -194,7 +178,7 @@ function preserveContexts( ): FlexibleTruncationInfo { const typeToContextMap = new Map< ChatContextType, - string | AdditionalContentEntryAddition[] | RelevantTextDocumentAddition[] | undefined + string | AdditionalContentEntryAddition[] | RelevantTextDocumentAddition[] >([ [ChatContextType.UserInput, triggerPayload.message], [ChatContextType.CurrentFile, triggerPayload.fileText], @@ -212,21 +196,16 @@ function preserveContexts( } const contexts = typeToContextMap.get(contextType) - if (!contexts) { - getLogger().debug( - `Current request context size: type: ${contextType}, before: ${truncationInfo.sizeBefore}, after: ${truncationInfo.sizeAfter}` - ) - return truncationInfo - } - switch (contextType) { case ChatContextType.UserInput: truncationInfo = truncate(contexts as string, truncationInfo) triggerPayload.message = truncationInfo.textAfter + triggerPayload.contextLengths.truncatedUserInputContextLength = truncationInfo.sizeAfter break case ChatContextType.CurrentFile: truncationInfo = truncate(contexts as string, truncationInfo) triggerPayload.fileText = truncationInfo.textAfter + triggerPayload.contextLengths.truncatedCurrentFileContextLength = truncationInfo.sizeAfter break case ChatContextType.UserSpecificPrompts: truncationInfo = truncateUserSpecificContexts( @@ -234,6 +213,8 @@ function preserveContexts( truncationInfo, 'prompt' ) + triggerPayload.contextLengths.truncatedAdditionalContextLengths.promptContextLength = + truncationInfo.sizeAfter break case ChatContextType.UserSpecificRules: truncationInfo = truncateUserSpecificContexts( @@ -241,6 +222,7 @@ function preserveContexts( truncationInfo, 'rule' ) + triggerPayload.contextLengths.truncatedAdditionalContextLengths.ruleContextLength = truncationInfo.sizeAfter break case ChatContextType.UserSpecificFiles: truncationInfo = truncateUserSpecificContexts( @@ -248,9 +230,11 @@ function preserveContexts( truncationInfo, 'file' ) + triggerPayload.contextLengths.truncatedAdditionalContextLengths.fileContextLength = truncationInfo.sizeAfter break case ChatContextType.Workspace: truncationInfo = truncateWorkspaceContexts(contexts as RelevantTextDocumentAddition[], truncationInfo) + triggerPayload.contextLengths.truncatedWorkspaceContextLength = truncationInfo.sizeAfter break default: getLogger().warn(`Unexpected context type: ${contextType}`) diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 7166b9122b2..468d6a8a011 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -78,6 +78,7 @@ import { aditionalContentNameLimit, additionalContentInnerContextLimit, workspaceChunkMaxSize, + defaultContextLengths, } from '../../constants' import { ChatSession } from '../../clients/chat/v0/chat' @@ -720,13 +721,21 @@ export class ChatController { trigger: ChatTriggerType.ChatMessage, query: undefined, codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: context?.focusAreaContext?.extendedCodeBlock, + fileText: context?.focusAreaContext?.extendedCodeBlock ?? '', fileLanguage: context?.activeFileContext?.fileLanguage, filePath: context?.activeFileContext?.filePath, matchPolicy: context?.activeFileContext?.matchPolicy, codeQuery: context?.focusAreaContext?.names, userIntent: this.userIntentRecognizer.getFromContextMenuCommand(command), customization: getSelectedCustomization(), + additionalContents: [], + relevantTextDocuments: [], + documentReferences: [], + useRelevantDocuments: false, + contextLengths: { + ...defaultContextLengths, + }, + context: [], }, triggerID ) @@ -793,17 +802,25 @@ export class ChatController { return this.generateResponse( { - message: message.message, + message: message.message ?? '', trigger: ChatTriggerType.ChatMessage, query: message.message, codeSelection: lastTriggerEvent.context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: lastTriggerEvent.context?.focusAreaContext?.extendedCodeBlock, + fileText: lastTriggerEvent.context?.focusAreaContext?.extendedCodeBlock ?? '', fileLanguage: lastTriggerEvent.context?.activeFileContext?.fileLanguage, filePath: lastTriggerEvent.context?.activeFileContext?.filePath, matchPolicy: lastTriggerEvent.context?.activeFileContext?.matchPolicy, codeQuery: lastTriggerEvent.context?.focusAreaContext?.names, userIntent: message.userIntent, customization: getSelectedCustomization(), + contextLengths: { + ...defaultContextLengths, + }, + relevantTextDocuments: [], + additionalContents: [], + documentReferences: [], + useRelevantDocuments: false, + context: [], }, triggerID ) @@ -826,18 +843,25 @@ export class ChatController { }) return this.generateResponse( { - message: message.message, + message: message.message ?? '', trigger: ChatTriggerType.ChatMessage, query: message.message, codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: context?.focusAreaContext?.extendedCodeBlock, + fileText: context?.focusAreaContext?.extendedCodeBlock ?? '', fileLanguage: context?.activeFileContext?.fileLanguage, filePath: context?.activeFileContext?.filePath, matchPolicy: context?.activeFileContext?.matchPolicy, codeQuery: context?.focusAreaContext?.names, userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message), customization: getSelectedCustomization(), - context: message.context, + context: message.context ?? [], + relevantTextDocuments: [], + additionalContents: [], + documentReferences: [], + useRelevantDocuments: false, + contextLengths: { + ...defaultContextLengths, + }, }, triggerID ) @@ -928,17 +952,16 @@ export class ChatController { triggerPayload.workspaceRulesCount = workspaceRules.length // Add context commands added by user to context - if (triggerPayload.context !== undefined && triggerPayload.context.length > 0) { - for (const context of triggerPayload.context) { - if (typeof context !== 'string' && context.route && context.route.length === 2) { - contextCommands.push({ - workspaceFolder: context.route[0] || '', - type: context.icon === 'folder' ? 'folder' : 'file', - relativePath: context.route[1] || '', - }) - } + for (const context of triggerPayload.context) { + if (typeof context !== 'string' && context.route && context.route.length === 2) { + contextCommands.push({ + workspaceFolder: context.route[0] || '', + type: context.icon === 'folder' ? 'folder' : 'file', + relativePath: context.route[1] || '', + }) } } + if (contextCommands.length === 0) { return [] } @@ -963,48 +986,29 @@ export class ChatController { getLogger().verbose(`Could not get context command prompts: ${e}`) } - triggerPayload.additionalContents = [] - const emptyLengths = { - fileContextLength: 0, - promptContextLength: 0, - ruleContextLength: 0, - } - triggerPayload.additionalContextLengths = emptyLengths - triggerPayload.truncatedAdditionalContextLengths = emptyLengths - - if (Array.isArray(prompts) && prompts.length > 0) { - triggerPayload.additionalContextLengths = this.telemetryHelper.getContextLengths(prompts) - for (const prompt of prompts.slice(0, 20)) { - // Add system prompt for user prompts and workspace rules - const contextType = this.telemetryHelper.getContextType(prompt) - const description = - contextType === 'rule' || contextType === 'prompt' - ? `You must follow the instructions in ${prompt.relativePath}. Below are lines ${prompt.startLine}-${prompt.endLine} of this file:\n` - : prompt.description - - // Handle user prompts outside the workspace - const relativePath = prompt.filePath.startsWith(getUserPromptsDirectory()) - ? path.basename(prompt.filePath) - : path.relative(workspaceFolder, prompt.filePath) - - const entry = { - name: prompt.name.substring(0, aditionalContentNameLimit), - description: description.substring(0, aditionalContentNameLimit), - innerContext: prompt.content.substring(0, additionalContentInnerContextLimit), - type: contextType, - relativePath: relativePath, - } - - if (contextType === 'rule') { - triggerPayload.truncatedAdditionalContextLengths.ruleContextLength += entry.innerContext.length - } else if (contextType === 'prompt') { - triggerPayload.truncatedAdditionalContextLengths.promptContextLength += entry.innerContext.length - } else if (contextType === 'file') { - triggerPayload.truncatedAdditionalContextLengths.fileContextLength += entry.innerContext.length - } - - triggerPayload.additionalContents.push(entry) + triggerPayload.contextLengths.additionalContextLengths = this.telemetryHelper.getContextLengths(prompts) + for (const prompt of prompts.slice(0, 20)) { + // Add system prompt for user prompts and workspace rules + const contextType = this.telemetryHelper.getContextType(prompt) + const description = + contextType === 'rule' || contextType === 'prompt' + ? `You must follow the instructions in ${prompt.relativePath}. Below are lines ${prompt.startLine}-${prompt.endLine} of this file:\n` + : prompt.description + + // Handle user prompts outside the workspace + const relativePath = prompt.filePath.startsWith(getUserPromptsDirectory()) + ? path.basename(prompt.filePath) + : path.relative(workspaceFolder, prompt.filePath) + + const entry = { + name: prompt.name.substring(0, aditionalContentNameLimit), + description: description.substring(0, aditionalContentNameLimit), + innerContext: prompt.content.substring(0, additionalContentInnerContextLimit), + type: contextType, + relativePath: relativePath, } + + triggerPayload.additionalContents.push(entry) } getLogger().info(`Retrieved chunks of additional context count: ${triggerPayload.additionalContents.length} `) } @@ -1044,19 +1048,17 @@ export class ChatController { const session = this.sessionStorage.getSession(tabID) await this.resolveContextCommandPayload(triggerPayload, session) - triggerPayload.useRelevantDocuments = - triggerPayload.context?.some( - (context) => typeof context !== 'string' && context.command === '@workspace' - ) || false - triggerPayload.documentReferences = [] - if (triggerPayload.useRelevantDocuments && triggerPayload.message) { + triggerPayload.useRelevantDocuments = triggerPayload.context.some( + (context) => typeof context !== 'string' && context.command === '@workspace' + ) + if (triggerPayload.useRelevantDocuments) { triggerPayload.message = triggerPayload.message.replace(/@workspace/, '') if (CodeWhispererSettings.instance.isLocalIndexEnabled()) { const start = performance.now() - triggerPayload.relevantTextDocuments = [] const relevantTextDocuments = await LspController.instance.query(triggerPayload.message) for (const relevantDocument of relevantTextDocuments) { - if (relevantDocument.text !== undefined && relevantDocument.text.length > 0) { + if (relevantDocument.text && relevantDocument.text.length > 0) { + triggerPayload.contextLengths.workspaceContextLength += relevantDocument.text.length if (relevantDocument.text.length > workspaceChunkMaxSize) { relevantDocument.text = relevantDocument.text.substring(0, workspaceChunkMaxSize) getLogger().debug(`Truncating @workspace chunk: ${relevantDocument.relativeFilePath} `) @@ -1077,36 +1079,31 @@ export class ChatController { } } + triggerPayload.contextLengths.userInputContextLength = triggerPayload.message.length + triggerPayload.contextLengths.currentFileContextLength = triggerPayload.fileText.length const request = triggerPayloadToChatRequest(triggerPayload) - triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments || []) + triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments) // Update context transparency after it's truncated dynamically to show users only the context sent. - if (triggerPayload.documentReferences !== undefined) { - const relativePathsOfMergedRelevantDocuments = triggerPayload.documentReferences.map( - (doc) => doc.relativeFilePath - ) - const seen: string[] = [] - if (triggerPayload.additionalContents) { - for (const additionalContent of triggerPayload.additionalContents) { - const relativePath = additionalContent.relativePath - if ( - !relativePathsOfMergedRelevantDocuments.includes(relativePath) && - !seen.includes(relativePath) - ) { - triggerPayload.documentReferences.push({ - relativeFilePath: relativePath, - lineRanges: [{ first: -1, second: -1 }], - }) - seen.push(relativePath) - } - } - } - for (const doc of triggerPayload.documentReferences) { - session.contexts.set(doc.relativeFilePath, doc.lineRanges) + const relativePathsOfMergedRelevantDocuments = triggerPayload.documentReferences.map( + (doc) => doc.relativeFilePath + ) + const seen: string[] = [] + for (const additionalContent of triggerPayload.additionalContents) { + const relativePath = additionalContent.relativePath + if (!relativePathsOfMergedRelevantDocuments.includes(relativePath) && !seen.includes(relativePath)) { + triggerPayload.documentReferences.push({ + relativeFilePath: relativePath, + lineRanges: [{ first: -1, second: -1 }], + }) + seen.push(relativePath) } } + for (const doc of triggerPayload.documentReferences) { + session.contexts.set(doc.relativeFilePath, doc.lineRanges) + } - getLogger().info( + getLogger().debug( `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: ${inspect(request, { depth: 12, })}` diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index 4a68040660e..fe4226894d3 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -178,27 +178,37 @@ export interface TriggerPayload { readonly query: string | undefined readonly codeSelection: Selection | undefined readonly trigger: ChatTriggerType - fileText: string | undefined + fileText: string readonly fileLanguage: string | undefined readonly filePath: string | undefined - message: string | undefined + message: string readonly matchPolicy: MatchPolicy | undefined readonly codeQuery: CodeQuery | undefined readonly userIntent: UserIntent | undefined readonly customization: Customization - readonly context?: string[] | QuickActionCommand[] - relevantTextDocuments?: RelevantTextDocumentAddition[] - additionalContents?: AdditionalContentEntryAddition[] + readonly context: string[] | QuickActionCommand[] + relevantTextDocuments: RelevantTextDocumentAddition[] + additionalContents: AdditionalContentEntryAddition[] // a reference to all documents used in chat payload // for providing better context transparency - documentReferences?: DocumentReference[] - useRelevantDocuments?: boolean + documentReferences: DocumentReference[] + useRelevantDocuments: boolean traceId?: string - additionalContextLengths?: AdditionalContextLengths - truncatedAdditionalContextLengths?: AdditionalContextLengths + contextLengths: ContextLengths workspaceRulesCount?: number } +export type ContextLengths = { + additionalContextLengths: AdditionalContextLengths + truncatedAdditionalContextLengths: AdditionalContextLengths + workspaceContextLength: number + truncatedWorkspaceContextLength: number + userInputContextLength: number + truncatedUserInputContextLength: number + currentFileContextLength: number + truncatedCurrentFileContextLength: number +} + export type AdditionalContextLengths = { fileContextLength: number promptContextLength: number diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index 2496caff07b..6b10cb0728c 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -506,7 +506,7 @@ export class CWCTelemetryHelper { cwsprChatUserIntent: this.getUserIntentForTelemetry(triggerPayload.userIntent), cwsprChatHasCodeSnippet: triggerPayload.codeSelection && !triggerPayload.codeSelection.isEmpty, cwsprChatProgrammingLanguage: triggerPayload.fileLanguage, - cwsprChatActiveEditorTotalCharacters: triggerPayload.fileText?.length, + cwsprChatActiveEditorTotalCharacters: triggerPayload.fileText.length, cwsprChatActiveEditorImportCount: triggerPayload.codeQuery?.fullyQualifiedNames?.used?.length, cwsprChatResponseCodeSnippetCount: message.totalNumberOfCodeBlocksInResponse, cwsprChatResponseCode: message.responseCode, @@ -523,26 +523,32 @@ export class CWCTelemetryHelper { cwsprChatFullServerResponseLatency: this.conversationStreamTotalTime.get(message.tabID) ?? 0, cwsprChatTimeBetweenDisplays: JSON.stringify(this.getTimeBetweenChunks(tabID, this.displayTimeForChunks)), cwsprChatFullDisplayLatency: fullDisplayLatency, - cwsprChatRequestLength: triggerPayload.message?.length ?? 0, + cwsprChatRequestLength: triggerPayload.message.length, cwsprChatResponseLength: message.messageLength, cwsprChatConversationType: 'Chat', credentialStartUrl: AuthUtil.instance.startUrl, codewhispererCustomizationArn: triggerPayload.customization.arn, cwsprChatHasProjectContext: hasProjectLevelContext, - cwsprChatHasContextList: triggerPayload.documentReferences && triggerPayload.documentReferences.length > 0, + cwsprChatHasContextList: triggerPayload.documentReferences.length > 0, cwsprChatFolderContextCount: contextCounts.folderContextCount, cwsprChatFileContextCount: contextCounts.fileContextCount, - cwsprChatFileContextLength: triggerPayload.additionalContextLengths?.fileContextLength ?? 0, + cwsprChatFileContextLength: triggerPayload.contextLengths.additionalContextLengths.fileContextLength, cwsprChatFileContextTruncatedLength: - triggerPayload.truncatedAdditionalContextLengths?.fileContextLength ?? 0, + triggerPayload.contextLengths.truncatedAdditionalContextLengths.fileContextLength, cwsprChatRuleContextCount: triggerPayload.workspaceRulesCount, - cwsprChatRuleContextLength: triggerPayload.additionalContextLengths?.ruleContextLength ?? 0, + cwsprChatRuleContextLength: triggerPayload.contextLengths.additionalContextLengths.ruleContextLength, cwsprChatRuleContextTruncatedLength: - triggerPayload.truncatedAdditionalContextLengths?.ruleContextLength ?? 0, + triggerPayload.contextLengths.truncatedAdditionalContextLengths.ruleContextLength, cwsprChatPromptContextCount: contextCounts.promptContextCount, - cwsprChatPromptContextLength: triggerPayload.additionalContextLengths?.promptContextLength ?? 0, + cwsprChatPromptContextLength: triggerPayload.contextLengths.additionalContextLengths.promptContextLength, cwsprChatPromptContextTruncatedLength: - triggerPayload.truncatedAdditionalContextLengths?.promptContextLength ?? 0, + triggerPayload.contextLengths.truncatedAdditionalContextLengths.promptContextLength, + cwsprChatCurrentFileContextLength: triggerPayload.contextLengths.currentFileContextLength, + cwsprChatCurrentFileContextTruncatedLength: triggerPayload.contextLengths.truncatedCurrentFileContextLength, + cwsprChatUserInputContextLength: triggerPayload.contextLengths.userInputContextLength, + cwsprChatUserInputContextTruncatedLength: triggerPayload.contextLengths.truncatedUserInputContextLength, + cwsprChatWorkspaceContextLength: triggerPayload.contextLengths.workspaceContextLength, + cwsprChatWorkspaceContextTruncatedLength: triggerPayload.contextLengths.truncatedWorkspaceContextLength, traceId, } diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index 4bc7052e865..1258cab5d56 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -228,6 +228,36 @@ "name": "executedCount", "type": "int", "description": "The number of executed operations" + }, + { + "name": "cwsprChatCurrentFileContextLength", + "type": "int", + "description": "cwsprChatCurrentFileContextLength" + }, + { + "name": "cwsprChatCurrentFileContextTruncatedLength", + "type": "int", + "description": "cwsprChatCurrentFileContextTruncatedLength" + }, + { + "name": "cwsprChatUserInputContextLength", + "type": "int", + "description": "cwsprChatUserInputContextLength" + }, + { + "name": "cwsprChatUserInputContextTruncatedLength", + "type": "int", + "description": "cwsprChatUserInputContextTruncatedLength" + }, + { + "name": "cwsprChatWorkspaceContextLength", + "type": "int", + "description": "cwsprChatWorkspaceContextLength" + }, + { + "name": "cwsprChatWorkspaceContextTruncatedLength", + "type": "int", + "description": "cwsprChatWorkspaceContextTruncatedLength" } ], "metrics": [ @@ -1116,6 +1146,183 @@ { "name": "docdb_addRegion", "description": "User clicked on add region command" + }, + { + "name": "amazonq_addMessage", + "description": "When a message is added to the conversation", + "metadata": [ + { + "type": "codewhispererCustomizationArn", + "required": false + }, + { + "type": "credentialStartUrl", + "required": false + }, + { + "type": "cwsprChatActiveEditorImportCount", + "required": false + }, + { + "type": "cwsprChatActiveEditorTotalCharacters", + "required": false + }, + { + "type": "cwsprChatConversationId" + }, + { + "type": "cwsprChatConversationType" + }, + { + "type": "cwsprChatCurrentFileContextLength", + "required": false + }, + { + "type": "cwsprChatCurrentFileContextTruncatedLength", + "required": false + }, + { + "type": "cwsprChatFileContextCount", + "required": false + }, + { + "type": "cwsprChatFileContextLength", + "required": false + }, + { + "type": "cwsprChatFileContextTruncatedLength", + "required": false + }, + { + "type": "cwsprChatFolderContextCount", + "required": false + }, + { + "type": "cwsprChatFollowUpCount", + "required": false + }, + { + "type": "cwsprChatFullDisplayLatency", + "required": false + }, + { + "type": "cwsprChatFullResponseLatency" + }, + { + "type": "cwsprChatFullServerResponseLatency", + "required": false + }, + { + "type": "cwsprChatHasCodeSnippet", + "required": false + }, + { + "type": "cwsprChatHasContextList", + "required": false + }, + { + "type": "cwsprChatHasProjectContext", + "required": false + }, + { + "type": "cwsprChatMessageId" + }, + { + "type": "cwsprChatProgrammingLanguage", + "required": false + }, + { + "type": "cwsprChatProjectContextQueryMs", + "required": false + }, + { + "type": "cwsprChatPromptContextCount", + "required": false + }, + { + "type": "cwsprChatPromptContextLength", + "required": false + }, + { + "type": "cwsprChatPromptContextTruncatedLength", + "required": false + }, + { + "type": "cwsprChatReferencesCount", + "required": false + }, + { + "type": "cwsprChatRequestLength" + }, + { + "type": "cwsprChatResponseCode" + }, + { + "type": "cwsprChatResponseCodeSnippetCount", + "required": false + }, + { + "type": "cwsprChatResponseLength", + "required": false + }, + { + "type": "cwsprChatRuleContextCount", + "required": false + }, + { + "type": "cwsprChatRuleContextLength", + "required": false + }, + { + "type": "cwsprChatRuleContextTruncatedLength", + "required": false + }, + { + "type": "cwsprChatSourceLinkCount", + "required": false + }, + { + "type": "cwsprChatTimeBetweenChunks" + }, + { + "type": "cwsprChatTimeBetweenDisplays", + "required": false + }, + { + "type": "cwsprChatTimeToFirstChunk" + }, + { + "type": "cwsprChatTimeToFirstDisplay", + "required": false + }, + { + "type": "cwsprChatTimeToFirstUsableChunk", + "required": false + }, + { + "type": "cwsprChatTriggerInteraction" + }, + { + "type": "cwsprChatUserInputContextLength", + "required": false + }, + { + "type": "cwsprChatUserInputContextTruncatedLength", + "required": false + }, + { + "type": "cwsprChatUserIntent", + "required": false + }, + { + "type": "cwsprChatWorkspaceContextLength", + "required": false + }, + { + "type": "cwsprChatWorkspaceContextTruncatedLength", + "required": false + } + ] } ] } From 0b715c080e5eb125d99e2f81eab3f90794b9e3bd Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 04:34:25 -0700 Subject: [PATCH 07/18] Add unit tests for truncation logic --- .../chat/chatRequest/converter.test.ts | 371 ++++++++++++++++++ .../controllers/chat/chatRequest/converter.ts | 2 +- 2 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts new file mode 100644 index 00000000000..e63b83e73ef --- /dev/null +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -0,0 +1,371 @@ +import assert from 'assert' +import { ChatTriggerType, TriggerPayload, triggerPayloadToChatRequest } from 'aws-core-vscode/codewhispererChat' +import { filePathSizeLimit } from '../../../../../../../core/dist/src/codewhispererChat/controllers/chat/chatRequest/converter' + +describe('triggerPayloadToChatRequest', () => { + const mockBasicPayload: TriggerPayload = { + message: 'test message', + filePath: 'test/path.ts', + fileText: 'console.log("hello")', + fileLanguage: 'typescript', + additionalContents: [], + relevantTextDocuments: [], + useRelevantDocuments: false, + customization: { arn: 'test:arn' }, + trigger: ChatTriggerType.ChatMessage, + contextLengths: { + truncatedUserInputContextLength: 0, + truncatedCurrentFileContextLength: 0, + truncatedWorkspaceContextLength: 0, + truncatedAdditionalContextLengths: { + promptContextLength: 0, + ruleContextLength: 0, + fileContextLength: 0, + }, + additionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + workspaceContextLength: 0, + userInputContextLength: 0, + currentFileContextLength: 0, + }, + context: [], + documentReferences: [], + query: undefined, + codeSelection: undefined, + matchPolicy: undefined, + codeQuery: undefined, + userIntent: undefined, + } + + it('should convert basic trigger payload to chat request', () => { + const result = triggerPayloadToChatRequest(mockBasicPayload) + + assert.notEqual(result, undefined) + assert.strictEqual(result.conversationState.currentMessage?.userInputMessage?.content, 'test message') + assert.strictEqual(result.conversationState.chatTriggerType, 'MANUAL') + assert.strictEqual(result.conversationState.customizationArn, 'test:arn') + }) + + it('should handle empty file path', () => { + const emptyFilePathPayload = { + ...mockBasicPayload, + filePath: '', + } + + const result = triggerPayloadToChatRequest(emptyFilePathPayload) + + assert.strictEqual( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document, + undefined + ) + }) + + it('should filter out empty additional contents', () => { + const payloadWithEmptyContents: TriggerPayload = { + ...mockBasicPayload, + additionalContents: [ + { name: 'prompt1', description: 'prompt1', relativePath: 'path1', type: 'prompt', innerContext: '' }, + { + name: 'prompt2', + description: 'prompt2', + relativePath: 'path2', + type: 'prompt', + innerContext: 'valid content', + }, + ], + } + + const result = triggerPayloadToChatRequest(payloadWithEmptyContents) + + assert.strictEqual( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext + ?.length, + 1 + ) + assert.strictEqual( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext.additionalContext?.[0] + .innerContext, + 'valid content' + ) + }) + + it('should handle unsupported programming language', () => { + const unsupportedLanguagePayload = { + ...mockBasicPayload, + fileLanguage: 'unsupported', + } + + const result = triggerPayloadToChatRequest(unsupportedLanguagePayload) + + assert.strictEqual( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.programmingLanguage, + undefined + ) + }) + + it('should truncate file path if it exceeds limit', () => { + const longFilePath = 'a'.repeat(5000) + const longFilePathPayload = { + ...mockBasicPayload, + filePath: longFilePath, + } + + const result = triggerPayloadToChatRequest(longFilePathPayload) + + assert.strictEqual( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.relativeFilePath?.length, + filePathSizeLimit + ) + }) +}) + +describe('Context Priority Truncation Tests', () => { + const createLargeString = (size: number, prefix: string = '') => prefix + 'x'.repeat(size - prefix.length) + + const createBaseTriggerPayload = (): TriggerPayload => ({ + message: '', + fileText: '', + filePath: 'test.ts', + fileLanguage: 'typescript', + trigger: ChatTriggerType.ChatMessage, + customization: { arn: '' }, + relevantTextDocuments: [], + additionalContents: [], + useRelevantDocuments: true, + contextLengths: { + truncatedUserInputContextLength: 0, + truncatedCurrentFileContextLength: 0, + truncatedWorkspaceContextLength: 0, + truncatedAdditionalContextLengths: { + promptContextLength: 0, + ruleContextLength: 0, + fileContextLength: 0, + }, + additionalContextLengths: { + fileContextLength: 0, + promptContextLength: 0, + ruleContextLength: 0, + }, + workspaceContextLength: 0, + userInputContextLength: 0, + currentFileContextLength: 0, + }, + query: undefined, + codeSelection: undefined, + matchPolicy: undefined, + codeQuery: undefined, + userIntent: undefined, + context: [], + documentReferences: [], + }) + + it('should preserve Type A (user input) over all other types when size exceeds limit', () => { + const payload = createBaseTriggerPayload() + const userInputSize = 60_000 + const promptSize = 30_000 + const currentFileSize = 20_000 + + payload.message = createLargeString(userInputSize, 'userInput-') + payload.additionalContents = [ + { + name: 'prompt1', + description: 'prompt1', + relativePath: 'path1', + type: 'prompt', + innerContext: createLargeString(promptSize, 'prompt-'), + }, + ] + payload.fileText = createLargeString(currentFileSize, 'currentFile-') + + const result = triggerPayloadToChatRequest(payload) + + // User input should be preserved completely + assert.strictEqual(result.conversationState.currentMessage?.userInputMessage?.content?.length, userInputSize) + + // Other contexts should be truncated + assert.ok( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.[0] + .innerContext?.length! < promptSize + ) + assert.ok( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.text?.length! < currentFileSize + ) + }) + + it('should preserve Type B1(prompts) over lower priority contexts when size exceeds limit', () => { + const payload = createBaseTriggerPayload() + const promptSize = 50_000 + const currentFileSize = 40_000 + const ruleSize = 30_000 + + payload.additionalContents = [ + { + name: 'prompt', + description: 'prompt', + relativePath: 'path1', + type: 'prompt', + innerContext: createLargeString(promptSize, 'prompt-'), + }, + { + name: 'rule', + description: 'rule', + relativePath: 'path2', + type: 'rule', + innerContext: createLargeString(ruleSize, 'rule-'), + }, + ] + payload.fileText = createLargeString(currentFileSize, 'currentFile-') + + const result = triggerPayloadToChatRequest(payload) + + // Prompt context should be preserved more than others + const promptContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c?.name === 'prompt' + )?.innerContext + const ruleContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c?.name === 'rule' + )?.innerContext + + assert.ok(promptContext!.length > ruleContext!.length) + assert.ok( + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.text?.length! < currentFileSize + ) + }) + + it('should preserve Type C (current file) over B1(rules), B2(files), and B3(workspace)', () => { + const payload = createBaseTriggerPayload() + const currentFileSize = 40_000 + const ruleSize = 30_000 + const fileSize = 20_000 + const workspaceSize = 10_000 + + payload.fileText = createLargeString(currentFileSize, 'currentFile-') + payload.additionalContents = [ + { + name: 'rule', + description: 'rule', + relativePath: 'path1', + type: 'rule', + innerContext: createLargeString(ruleSize, 'rule-'), + }, + { + name: 'file', + description: 'file', + relativePath: 'path2', + type: 'file', + innerContext: createLargeString(fileSize, 'file-'), + }, + ] + payload.relevantTextDocuments = [ + { + relativeFilePath: 'workspace.ts', + text: createLargeString(workspaceSize, 'workspace-'), + startLine: -1, + endLine: -1, + }, + ] + + const result = triggerPayloadToChatRequest(payload) + + const currentFileLength = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.text?.length! + const ruleContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c.name === 'rule' + )?.innerContext + const fileContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c.name === 'file' + )?.innerContext + const workspaceContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState + ?.relevantDocuments?.[0].text + + assert.ok(currentFileLength > ruleContext!.length) + assert.ok(ruleContext!.length > fileContext!.length) + assert.ok(fileContext!.length > workspaceContext!.length) + }) + + it('should preserve priority order when all context types are present', () => { + const payload = createBaseTriggerPayload() + const userInputSize = 30_000 + const promptSize = 25_000 + const currentFileSize = 20_000 + const ruleSize = 15_000 + const fileSize = 10_000 + const workspaceSize = 5_000 + + payload.message = createLargeString(userInputSize, 'userInput-') + payload.additionalContents = [ + { + name: 'prompt', + description: 'prompt', + relativePath: 'path1', + type: 'prompt', + innerContext: createLargeString(promptSize, 'prompt-'), + }, + { + name: 'rule', + description: 'rule', + relativePath: 'path2', + type: 'rule', + innerContext: createLargeString(ruleSize, 'rule-'), + }, + { + name: 'file', + description: 'file', + relativePath: 'path3', + type: 'file', + innerContext: createLargeString(fileSize, 'file-'), + }, + ] + payload.fileText = createLargeString(currentFileSize, 'currentFile-') + payload.relevantTextDocuments = [ + { + relativeFilePath: 'workspace.ts', + text: createLargeString(workspaceSize, 'workspace-'), + startLine: -1, + endLine: -1, + }, + ] + + const result = triggerPayloadToChatRequest(payload) + + const userInputLength = result.conversationState.currentMessage?.userInputMessage?.content?.length! + const promptContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c.name === 'prompt' + )?.innerContext + const currentFileLength = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document + ?.text?.length! + const ruleContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c.name === 'rule' + )?.innerContext + const fileContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( + (c) => c.name === 'file' + )?.innerContext + const workspaceContext = + result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState + ?.relevantDocuments?.[0].text + + // Verify priority ordering + assert.ok(userInputLength >= promptContext!.length) + assert.ok(promptContext!.length >= currentFileLength) + assert.ok(currentFileLength >= ruleContext!.length) + assert.ok(ruleContext!.length >= fileContext!.length) + assert.ok(fileContext!.length >= workspaceContext!.length) + }) +}) diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index b098198bd46..31be5f1654e 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -28,7 +28,7 @@ export const supportedLanguagesList = [ 'sql', ] -const filePathSizeLimit = 4_000 +export const filePathSizeLimit = 4_000 export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { // Flexible truncation logic From 164e6da72711558988b51417645f9ed87b6df672 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 04:51:04 -0700 Subject: [PATCH 08/18] rename cwsprChatCurrentFileXXX to cwsprChatFocusFileXXX --- .../src/inlineChat/provider/inlineChatProvider.ts | 4 ++-- .../controllers/chat/chatRequest/converter.test.ts | 8 ++++---- packages/core/src/codewhispererChat/constants.ts | 4 ++-- .../controllers/chat/chatRequest/converter.ts | 4 ++-- .../codewhispererChat/controllers/chat/controller.ts | 2 +- .../src/codewhispererChat/controllers/chat/model.ts | 4 ++-- .../controllers/chat/telemetryHelper.ts | 4 ++-- .../core/src/shared/telemetry/vscodeTelemetry.json | 12 ++++++------ 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index cc8eed71162..af4ac95a036 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -86,8 +86,8 @@ export class InlineChatProvider { truncatedWorkspaceContextLength: 0, userInputContextLength: 0, truncatedUserInputContextLength: 0, - currentFileContextLength: 0, - truncatedCurrentFileContextLength: 0, + focusFileContextLength: 0, + truncatedFocusFileContextLength: 0, }, }, triggerID diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index e63b83e73ef..ae2487afce5 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -15,7 +15,7 @@ describe('triggerPayloadToChatRequest', () => { trigger: ChatTriggerType.ChatMessage, contextLengths: { truncatedUserInputContextLength: 0, - truncatedCurrentFileContextLength: 0, + truncatedFocusFileContextLength: 0, truncatedWorkspaceContextLength: 0, truncatedAdditionalContextLengths: { promptContextLength: 0, @@ -29,7 +29,7 @@ describe('triggerPayloadToChatRequest', () => { }, workspaceContextLength: 0, userInputContextLength: 0, - currentFileContextLength: 0, + focusFileContextLength: 0, }, context: [], documentReferences: [], @@ -139,7 +139,7 @@ describe('Context Priority Truncation Tests', () => { useRelevantDocuments: true, contextLengths: { truncatedUserInputContextLength: 0, - truncatedCurrentFileContextLength: 0, + truncatedFocusFileContextLength: 0, truncatedWorkspaceContextLength: 0, truncatedAdditionalContextLengths: { promptContextLength: 0, @@ -153,7 +153,7 @@ describe('Context Priority Truncation Tests', () => { }, workspaceContextLength: 0, userInputContextLength: 0, - currentFileContextLength: 0, + focusFileContextLength: 0, }, query: undefined, codeSelection: undefined, diff --git a/packages/core/src/codewhispererChat/constants.ts b/packages/core/src/codewhispererChat/constants.ts index bacc037007b..84dd2dae292 100644 --- a/packages/core/src/codewhispererChat/constants.ts +++ b/packages/core/src/codewhispererChat/constants.ts @@ -37,6 +37,6 @@ export const defaultContextLengths: ContextLengths = { truncatedWorkspaceContextLength: 0, userInputContextLength: 0, truncatedUserInputContextLength: 0, - currentFileContextLength: 0, - truncatedCurrentFileContextLength: 0, + focusFileContextLength: 0, + truncatedFocusFileContextLength: 0, } diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index 31be5f1654e..be286122dc6 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -205,7 +205,7 @@ function preserveContexts( case ChatContextType.CurrentFile: truncationInfo = truncate(contexts as string, truncationInfo) triggerPayload.fileText = truncationInfo.textAfter - triggerPayload.contextLengths.truncatedCurrentFileContextLength = truncationInfo.sizeAfter + triggerPayload.contextLengths.truncatedFocusFileContextLength = truncationInfo.sizeAfter break case ChatContextType.UserSpecificPrompts: truncationInfo = truncateUserSpecificContexts( @@ -279,7 +279,7 @@ function truncateWorkspaceContexts( function truncate( textBefore: string, truncationInfo: FlexibleTruncationInfo, - isCurrentFile: Boolean = false + isCurrentFile: boolean = false ): FlexibleTruncationInfo { const sizeBefore = truncationInfo.sizeBefore + textBefore.length diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 468d6a8a011..8bcc36d835d 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -1080,7 +1080,7 @@ export class ChatController { } triggerPayload.contextLengths.userInputContextLength = triggerPayload.message.length - triggerPayload.contextLengths.currentFileContextLength = triggerPayload.fileText.length + triggerPayload.contextLengths.focusFileContextLength = triggerPayload.fileText.length const request = triggerPayloadToChatRequest(triggerPayload) triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments) diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index fe4226894d3..92878c515d9 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -205,8 +205,8 @@ export type ContextLengths = { truncatedWorkspaceContextLength: number userInputContextLength: number truncatedUserInputContextLength: number - currentFileContextLength: number - truncatedCurrentFileContextLength: number + focusFileContextLength: number + truncatedFocusFileContextLength: number } export type AdditionalContextLengths = { diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index 6b10cb0728c..5d5cc09056d 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -543,8 +543,8 @@ export class CWCTelemetryHelper { cwsprChatPromptContextLength: triggerPayload.contextLengths.additionalContextLengths.promptContextLength, cwsprChatPromptContextTruncatedLength: triggerPayload.contextLengths.truncatedAdditionalContextLengths.promptContextLength, - cwsprChatCurrentFileContextLength: triggerPayload.contextLengths.currentFileContextLength, - cwsprChatCurrentFileContextTruncatedLength: triggerPayload.contextLengths.truncatedCurrentFileContextLength, + cwsprChatFocusFileContextLength: triggerPayload.contextLengths.focusFileContextLength, + cwsprChatFocusFileContextTruncatedLength: triggerPayload.contextLengths.truncatedFocusFileContextLength, cwsprChatUserInputContextLength: triggerPayload.contextLengths.userInputContextLength, cwsprChatUserInputContextTruncatedLength: triggerPayload.contextLengths.truncatedUserInputContextLength, cwsprChatWorkspaceContextLength: triggerPayload.contextLengths.workspaceContextLength, diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index 1258cab5d56..ad8e79856d4 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -230,14 +230,14 @@ "description": "The number of executed operations" }, { - "name": "cwsprChatCurrentFileContextLength", + "name": "cwsprChatFocusFileContextLength", "type": "int", - "description": "cwsprChatCurrentFileContextLength" + "description": "cwsprChatFocusFileContextLength" }, { - "name": "cwsprChatCurrentFileContextTruncatedLength", + "name": "cwsprChatFocusFileContextTruncatedLength", "type": "int", - "description": "cwsprChatCurrentFileContextTruncatedLength" + "description": "cwsprChatFocusFileContextTruncatedLength" }, { "name": "cwsprChatUserInputContextLength", @@ -1174,11 +1174,11 @@ "type": "cwsprChatConversationType" }, { - "type": "cwsprChatCurrentFileContextLength", + "type": "cwsprChatFocusFileContextLength", "required": false }, { - "type": "cwsprChatCurrentFileContextTruncatedLength", + "type": "cwsprChatFocusFileContextTruncatedLength", "required": false }, { From af069e48d6e763632a01dbc00d94e17622bfb6ab Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 05:07:17 -0700 Subject: [PATCH 09/18] fix test duplicated code --- .../chat/chatRequest/converter.test.ts | 149 ++++++------------ 1 file changed, 44 insertions(+), 105 deletions(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index ae2487afce5..823ad28e305 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -40,6 +40,46 @@ describe('triggerPayloadToChatRequest', () => { userIntent: undefined, } + const createLargeString = (size: number, prefix: string = '') => prefix + 'x'.repeat(size - prefix.length) + + const createPrompt = (size: number) => { + return { + name: 'prompt', + description: 'prompt', + relativePath: 'path-prompt', + type: 'prompt', + innerContext: createLargeString(size, 'prompt-'), + } + } + + const createRule = (size: number) => { + return { + name: 'rule', + description: 'rule', + relativePath: 'path-rule', + type: 'rule', + innerContext: createLargeString(size, 'rule-'), + } + } + + const createFile = (size: number) => { + return { + name: 'file', + description: 'file', + relativePath: 'path-file', + type: 'file', + innerContext: createLargeString(size, 'file-'), + } + } + + const createBaseTriggerPayload = (): TriggerPayload => ({ + ...mockBasicPayload, + message: '', + fileText: '', + filePath: 'test.ts', + fileLanguage: 'typescript', + customization: { arn: '' }, + }) it('should convert basic trigger payload to chat request', () => { const result = triggerPayloadToChatRequest(mockBasicPayload) @@ -122,47 +162,6 @@ describe('triggerPayloadToChatRequest', () => { filePathSizeLimit ) }) -}) - -describe('Context Priority Truncation Tests', () => { - const createLargeString = (size: number, prefix: string = '') => prefix + 'x'.repeat(size - prefix.length) - - const createBaseTriggerPayload = (): TriggerPayload => ({ - message: '', - fileText: '', - filePath: 'test.ts', - fileLanguage: 'typescript', - trigger: ChatTriggerType.ChatMessage, - customization: { arn: '' }, - relevantTextDocuments: [], - additionalContents: [], - useRelevantDocuments: true, - contextLengths: { - truncatedUserInputContextLength: 0, - truncatedFocusFileContextLength: 0, - truncatedWorkspaceContextLength: 0, - truncatedAdditionalContextLengths: { - promptContextLength: 0, - ruleContextLength: 0, - fileContextLength: 0, - }, - additionalContextLengths: { - fileContextLength: 0, - promptContextLength: 0, - ruleContextLength: 0, - }, - workspaceContextLength: 0, - userInputContextLength: 0, - focusFileContextLength: 0, - }, - query: undefined, - codeSelection: undefined, - matchPolicy: undefined, - codeQuery: undefined, - userIntent: undefined, - context: [], - documentReferences: [], - }) it('should preserve Type A (user input) over all other types when size exceeds limit', () => { const payload = createBaseTriggerPayload() @@ -171,15 +170,7 @@ describe('Context Priority Truncation Tests', () => { const currentFileSize = 20_000 payload.message = createLargeString(userInputSize, 'userInput-') - payload.additionalContents = [ - { - name: 'prompt1', - description: 'prompt1', - relativePath: 'path1', - type: 'prompt', - innerContext: createLargeString(promptSize, 'prompt-'), - }, - ] + payload.additionalContents = [createPrompt(promptSize)] payload.fileText = createLargeString(currentFileSize, 'currentFile-') const result = triggerPayloadToChatRequest(payload) @@ -204,22 +195,7 @@ describe('Context Priority Truncation Tests', () => { const currentFileSize = 40_000 const ruleSize = 30_000 - payload.additionalContents = [ - { - name: 'prompt', - description: 'prompt', - relativePath: 'path1', - type: 'prompt', - innerContext: createLargeString(promptSize, 'prompt-'), - }, - { - name: 'rule', - description: 'rule', - relativePath: 'path2', - type: 'rule', - innerContext: createLargeString(ruleSize, 'rule-'), - }, - ] + payload.additionalContents = [createPrompt(promptSize), createRule(ruleSize)] payload.fileText = createLargeString(currentFileSize, 'currentFile-') const result = triggerPayloadToChatRequest(payload) @@ -249,22 +225,7 @@ describe('Context Priority Truncation Tests', () => { const workspaceSize = 10_000 payload.fileText = createLargeString(currentFileSize, 'currentFile-') - payload.additionalContents = [ - { - name: 'rule', - description: 'rule', - relativePath: 'path1', - type: 'rule', - innerContext: createLargeString(ruleSize, 'rule-'), - }, - { - name: 'file', - description: 'file', - relativePath: 'path2', - type: 'file', - innerContext: createLargeString(fileSize, 'file-'), - }, - ] + payload.additionalContents = [createRule(ruleSize), createFile(fileSize)] payload.relevantTextDocuments = [ { relativeFilePath: 'workspace.ts', @@ -306,29 +267,7 @@ describe('Context Priority Truncation Tests', () => { const workspaceSize = 5_000 payload.message = createLargeString(userInputSize, 'userInput-') - payload.additionalContents = [ - { - name: 'prompt', - description: 'prompt', - relativePath: 'path1', - type: 'prompt', - innerContext: createLargeString(promptSize, 'prompt-'), - }, - { - name: 'rule', - description: 'rule', - relativePath: 'path2', - type: 'rule', - innerContext: createLargeString(ruleSize, 'rule-'), - }, - { - name: 'file', - description: 'file', - relativePath: 'path3', - type: 'file', - innerContext: createLargeString(fileSize, 'file-'), - }, - ] + payload.additionalContents = [createPrompt(promptSize), createRule(ruleSize), createFile(fileSize)] payload.fileText = createLargeString(currentFileSize, 'currentFile-') payload.relevantTextDocuments = [ { From c9b2eec2ddc28872c8bf68febaafa5cefc6e5a83 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 05:24:48 -0700 Subject: [PATCH 10/18] remove dups again --- .../chat/chatRequest/converter.test.ts | 134 ++++-------------- 1 file changed, 29 insertions(+), 105 deletions(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 823ad28e305..6d19a57fd78 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -163,108 +163,31 @@ describe('triggerPayloadToChatRequest', () => { ) }) - it('should preserve Type A (user input) over all other types when size exceeds limit', () => { - const payload = createBaseTriggerPayload() - const userInputSize = 60_000 - const promptSize = 30_000 - const currentFileSize = 20_000 - - payload.message = createLargeString(userInputSize, 'userInput-') - payload.additionalContents = [createPrompt(promptSize)] - payload.fileText = createLargeString(currentFileSize, 'currentFile-') - - const result = triggerPayloadToChatRequest(payload) - - // User input should be preserved completely - assert.strictEqual(result.conversationState.currentMessage?.userInputMessage?.content?.length, userInputSize) - - // Other contexts should be truncated - assert.ok( - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.[0] - .innerContext?.length! < promptSize - ) - assert.ok( - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document - ?.text?.length! < currentFileSize - ) + it('should preserve priority order', () => { + const before1 = [5000, 30000, 40000, 20000, 15000, 25000] // Total: 135,000 + const after1 = [5000, 30000, 40000, 15000, 10000, 0] // Total: 100,000 + checkContextTruncationHelper(before1, after1) + + const before2 = [1000, 2000, 3000, 4000, 5000, 90000] // Total: 105,000 + const after2 = [1000, 2000, 3000, 4000, 5000, 85000] // Total: 100,000 + checkContextTruncationHelper(before2, after2) + + const before3 = [10000, 40000, 80000, 30000, 20000, 50000] // Total: 230,000 + const after3 = [10000, 40000, 50000, 0, 0, 0] // Total: 100,000 + checkContextTruncationHelper(before3, after3) + + const before4 = [5000, 5000, 150000, 5000, 5000, 5000] // Total: 175,000 + const after4 = [5000, 5000, 80000, 5000, 5000, 0] // Total: 100,000 + checkContextTruncationHelper(before4, after4) + + const before5 = [50000, 80000, 20000, 10000, 10000, 10000] // Total: 180,000 + const after5 = [50000, 50000, 0, 0, 0, 0] // Total: 100,000 + checkContextTruncationHelper(before5, after5) }) - it('should preserve Type B1(prompts) over lower priority contexts when size exceeds limit', () => { + function checkContextTruncationHelper(before: number[], after: number[]) { const payload = createBaseTriggerPayload() - const promptSize = 50_000 - const currentFileSize = 40_000 - const ruleSize = 30_000 - - payload.additionalContents = [createPrompt(promptSize), createRule(ruleSize)] - payload.fileText = createLargeString(currentFileSize, 'currentFile-') - - const result = triggerPayloadToChatRequest(payload) - - // Prompt context should be preserved more than others - const promptContext = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( - (c) => c?.name === 'prompt' - )?.innerContext - const ruleContext = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( - (c) => c?.name === 'rule' - )?.innerContext - - assert.ok(promptContext!.length > ruleContext!.length) - assert.ok( - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document - ?.text?.length! < currentFileSize - ) - }) - - it('should preserve Type C (current file) over B1(rules), B2(files), and B3(workspace)', () => { - const payload = createBaseTriggerPayload() - const currentFileSize = 40_000 - const ruleSize = 30_000 - const fileSize = 20_000 - const workspaceSize = 10_000 - - payload.fileText = createLargeString(currentFileSize, 'currentFile-') - payload.additionalContents = [createRule(ruleSize), createFile(fileSize)] - payload.relevantTextDocuments = [ - { - relativeFilePath: 'workspace.ts', - text: createLargeString(workspaceSize, 'workspace-'), - startLine: -1, - endLine: -1, - }, - ] - - const result = triggerPayloadToChatRequest(payload) - - const currentFileLength = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document - ?.text?.length! - const ruleContext = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( - (c) => c.name === 'rule' - )?.innerContext - const fileContext = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( - (c) => c.name === 'file' - )?.innerContext - const workspaceContext = - result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState - ?.relevantDocuments?.[0].text - - assert.ok(currentFileLength > ruleContext!.length) - assert.ok(ruleContext!.length > fileContext!.length) - assert.ok(fileContext!.length > workspaceContext!.length) - }) - - it('should preserve priority order when all context types are present', () => { - const payload = createBaseTriggerPayload() - const userInputSize = 30_000 - const promptSize = 25_000 - const currentFileSize = 20_000 - const ruleSize = 15_000 - const fileSize = 10_000 - const workspaceSize = 5_000 + const [userInputSize, promptSize, currentFileSize, ruleSize, fileSize, workspaceSize] = before payload.message = createLargeString(userInputSize, 'userInput-') payload.additionalContents = [createPrompt(promptSize), createRule(ruleSize), createFile(fileSize)] @@ -301,10 +224,11 @@ describe('triggerPayloadToChatRequest', () => { ?.relevantDocuments?.[0].text // Verify priority ordering - assert.ok(userInputLength >= promptContext!.length) - assert.ok(promptContext!.length >= currentFileLength) - assert.ok(currentFileLength >= ruleContext!.length) - assert.ok(ruleContext!.length >= fileContext!.length) - assert.ok(fileContext!.length >= workspaceContext!.length) - }) + assert.strictEqual(userInputLength, after[0]) + assert.strictEqual(promptContext?.length, after[1]) + assert.strictEqual(currentFileLength, after[2]) + assert.strictEqual(ruleContext?.length, after[3]) + assert.strictEqual(fileContext?.length, after[4]) + assert.strictEqual(workspaceContext?.length, after[5]) + } }) From 5563a3263b928f4729e34322fd58eefeef83b307 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 05:34:15 -0700 Subject: [PATCH 11/18] fix import --- .../controllers/chat/chatRequest/converter.test.ts | 8 ++++++-- packages/core/src/codewhispererChat/index.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 6d19a57fd78..d7c43eb111e 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -1,6 +1,10 @@ import assert from 'assert' -import { ChatTriggerType, TriggerPayload, triggerPayloadToChatRequest } from 'aws-core-vscode/codewhispererChat' -import { filePathSizeLimit } from '../../../../../../../core/dist/src/codewhispererChat/controllers/chat/chatRequest/converter' +import { + ChatTriggerType, + filePathSizeLimit, + TriggerPayload, + triggerPayloadToChatRequest, +} from 'aws-core-vscode/codewhispererChat' describe('triggerPayloadToChatRequest', () => { const mockBasicPayload: TriggerPayload = { diff --git a/packages/core/src/codewhispererChat/index.ts b/packages/core/src/codewhispererChat/index.ts index e473203caf5..b47115fbc4a 100644 --- a/packages/core/src/codewhispererChat/index.ts +++ b/packages/core/src/codewhispererChat/index.ts @@ -7,7 +7,7 @@ export { FocusAreaContextExtractor } from './editor/context/focusArea/focusAreaE export { TryChatCodeLensProvider, resolveModifierKey, tryChatCodeLensCommand } from './editor/codelens' export { focusAmazonQPanel } from './commands/registerCommands' export { ChatSession } from './clients/chat/v0/chat' -export { triggerPayloadToChatRequest } from './controllers/chat/chatRequest/converter' +export { triggerPayloadToChatRequest, filePathSizeLimit } from './controllers/chat/chatRequest/converter' export { ChatTriggerType, PromptMessage, TriggerPayload } from './controllers/chat/model' export { UserIntentRecognizer } from './controllers/chat/userIntent/userIntentRecognizer' export { EditorContextExtractor } from './editor/context/extractor' From 0a888d7df37a3ae7674e88b73f376cf28d0bc123 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 05:45:25 -0700 Subject: [PATCH 12/18] fix missing header --- .../controllers/chat/chatRequest/converter.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index d7c43eb111e..4c58a404d3d 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -1,3 +1,8 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + import assert from 'assert' import { ChatTriggerType, From 36dd41e11fce593b57dab2c092e47476beac0fd0 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 10:36:56 -0700 Subject: [PATCH 13/18] fix lint --- .../controllers/chat/chatRequest/converter.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 4c58a404d3d..17cd497a6d3 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -212,14 +212,14 @@ describe('triggerPayloadToChatRequest', () => { const result = triggerPayloadToChatRequest(payload) - const userInputLength = result.conversationState.currentMessage?.userInputMessage?.content?.length! + const userInputLength = result.conversationState.currentMessage?.userInputMessage?.content?.length const promptContext = result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( (c) => c.name === 'prompt' )?.innerContext const currentFileLength = result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document - ?.text?.length! + ?.text?.length const ruleContext = result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find( (c) => c.name === 'rule' From 9a0fd46cc6c08c9614a9d6ddb1c1d1a6bc7ac719 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 10:52:50 -0700 Subject: [PATCH 14/18] fix test error --- .../controllers/chat/chatRequest/converter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 17cd497a6d3..8f023bf1a95 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -230,7 +230,7 @@ describe('triggerPayloadToChatRequest', () => { )?.innerContext const workspaceContext = result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState - ?.relevantDocuments?.[0].text + ?.relevantDocuments?.[0]?.text // Verify priority ordering assert.strictEqual(userInputLength, after[0]) From 1be91202f3b84972319e8e42d79170491c0917cf Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 11:06:00 -0700 Subject: [PATCH 15/18] fix test --- .../controllers/chat/chatRequest/converter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 8f023bf1a95..06385d00f99 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -174,7 +174,7 @@ describe('triggerPayloadToChatRequest', () => { it('should preserve priority order', () => { const before1 = [5000, 30000, 40000, 20000, 15000, 25000] // Total: 135,000 - const after1 = [5000, 30000, 40000, 15000, 10000, 0] // Total: 100,000 + const after1 = [5000, 30000, 40000, 20000, 5000, 0] // Total: 100,000 checkContextTruncationHelper(before1, after1) const before2 = [1000, 2000, 3000, 4000, 5000, 90000] // Total: 105,000 From 03a83e2a4a45e4b7d81b376811d0bb8ef6ca9d94 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 11:48:09 -0700 Subject: [PATCH 16/18] fix tests --- .../controllers/chat/chatRequest/converter.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 06385d00f99..714586692a0 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -233,11 +233,11 @@ describe('triggerPayloadToChatRequest', () => { ?.relevantDocuments?.[0]?.text // Verify priority ordering - assert.strictEqual(userInputLength, after[0]) - assert.strictEqual(promptContext?.length, after[1]) - assert.strictEqual(currentFileLength, after[2]) - assert.strictEqual(ruleContext?.length, after[3]) - assert.strictEqual(fileContext?.length, after[4]) - assert.strictEqual(workspaceContext?.length, after[5]) + assert.strictEqual(userInputLength ?? 0, after[0]) + assert.strictEqual(promptContext?.length ?? 0, after[1]) + assert.strictEqual(currentFileLength ?? 0, after[2]) + assert.strictEqual(ruleContext?.length ?? 0, after[3]) + assert.strictEqual(fileContext?.length ?? 0, after[4]) + assert.strictEqual(workspaceContext?.length ?? 0, after[5]) } }) From c66376c0f5e13f89baa62ded8be0b51b3d14590f Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 12:29:37 -0700 Subject: [PATCH 17/18] fix tests --- .../controllers/chat/chatRequest/converter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 714586692a0..d18b916229f 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -186,7 +186,7 @@ describe('triggerPayloadToChatRequest', () => { checkContextTruncationHelper(before3, after3) const before4 = [5000, 5000, 150000, 5000, 5000, 5000] // Total: 175,000 - const after4 = [5000, 5000, 80000, 5000, 5000, 0] // Total: 100,000 + const after4 = [5000, 5000, 90000, 0, 0, 0] // Total: 100,000 checkContextTruncationHelper(before4, after4) const before5 = [50000, 80000, 20000, 10000, 10000, 10000] // Total: 180,000 From b8c58732eb172f9541fd01330338b1d1394ce6b8 Mon Sep 17 00:00:00 2001 From: Andrew Yu Date: Mon, 24 Mar 2025 14:29:18 -0700 Subject: [PATCH 18/18] remove telemetry manual override --- package-lock.json | 6 +- package.json | 2 +- .../src/shared/telemetry/vscodeTelemetry.json | 207 ------------------ 3 files changed, 5 insertions(+), 210 deletions(-) diff --git a/package-lock.json b/package-lock.json index f602ca6dbc0..49aac312dde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "vscode-nls-dev": "^4.0.4" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.308", + "@aws-toolkits/telemetry": "^1.0.311", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", @@ -10806,7 +10806,9 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.308", + "version": "1.0.311", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.311.tgz", + "integrity": "sha512-O8WhssqJLc6PPBwsONDPOmaZDGU56ZBRbRsyxw6faH/7aZEPOLQM6+ao9VhWeYfIkOy+yvcQiSc61zwM7DZ3OQ==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 1abf7ff4cf4..6364c093837 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "skippedTestReport": "ts-node ./scripts/skippedTestReport.ts ./packages/amazonq/test/e2e/" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.308", + "@aws-toolkits/telemetry": "^1.0.311", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index ad8e79856d4..4bc7052e865 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -228,36 +228,6 @@ "name": "executedCount", "type": "int", "description": "The number of executed operations" - }, - { - "name": "cwsprChatFocusFileContextLength", - "type": "int", - "description": "cwsprChatFocusFileContextLength" - }, - { - "name": "cwsprChatFocusFileContextTruncatedLength", - "type": "int", - "description": "cwsprChatFocusFileContextTruncatedLength" - }, - { - "name": "cwsprChatUserInputContextLength", - "type": "int", - "description": "cwsprChatUserInputContextLength" - }, - { - "name": "cwsprChatUserInputContextTruncatedLength", - "type": "int", - "description": "cwsprChatUserInputContextTruncatedLength" - }, - { - "name": "cwsprChatWorkspaceContextLength", - "type": "int", - "description": "cwsprChatWorkspaceContextLength" - }, - { - "name": "cwsprChatWorkspaceContextTruncatedLength", - "type": "int", - "description": "cwsprChatWorkspaceContextTruncatedLength" } ], "metrics": [ @@ -1146,183 +1116,6 @@ { "name": "docdb_addRegion", "description": "User clicked on add region command" - }, - { - "name": "amazonq_addMessage", - "description": "When a message is added to the conversation", - "metadata": [ - { - "type": "codewhispererCustomizationArn", - "required": false - }, - { - "type": "credentialStartUrl", - "required": false - }, - { - "type": "cwsprChatActiveEditorImportCount", - "required": false - }, - { - "type": "cwsprChatActiveEditorTotalCharacters", - "required": false - }, - { - "type": "cwsprChatConversationId" - }, - { - "type": "cwsprChatConversationType" - }, - { - "type": "cwsprChatFocusFileContextLength", - "required": false - }, - { - "type": "cwsprChatFocusFileContextTruncatedLength", - "required": false - }, - { - "type": "cwsprChatFileContextCount", - "required": false - }, - { - "type": "cwsprChatFileContextLength", - "required": false - }, - { - "type": "cwsprChatFileContextTruncatedLength", - "required": false - }, - { - "type": "cwsprChatFolderContextCount", - "required": false - }, - { - "type": "cwsprChatFollowUpCount", - "required": false - }, - { - "type": "cwsprChatFullDisplayLatency", - "required": false - }, - { - "type": "cwsprChatFullResponseLatency" - }, - { - "type": "cwsprChatFullServerResponseLatency", - "required": false - }, - { - "type": "cwsprChatHasCodeSnippet", - "required": false - }, - { - "type": "cwsprChatHasContextList", - "required": false - }, - { - "type": "cwsprChatHasProjectContext", - "required": false - }, - { - "type": "cwsprChatMessageId" - }, - { - "type": "cwsprChatProgrammingLanguage", - "required": false - }, - { - "type": "cwsprChatProjectContextQueryMs", - "required": false - }, - { - "type": "cwsprChatPromptContextCount", - "required": false - }, - { - "type": "cwsprChatPromptContextLength", - "required": false - }, - { - "type": "cwsprChatPromptContextTruncatedLength", - "required": false - }, - { - "type": "cwsprChatReferencesCount", - "required": false - }, - { - "type": "cwsprChatRequestLength" - }, - { - "type": "cwsprChatResponseCode" - }, - { - "type": "cwsprChatResponseCodeSnippetCount", - "required": false - }, - { - "type": "cwsprChatResponseLength", - "required": false - }, - { - "type": "cwsprChatRuleContextCount", - "required": false - }, - { - "type": "cwsprChatRuleContextLength", - "required": false - }, - { - "type": "cwsprChatRuleContextTruncatedLength", - "required": false - }, - { - "type": "cwsprChatSourceLinkCount", - "required": false - }, - { - "type": "cwsprChatTimeBetweenChunks" - }, - { - "type": "cwsprChatTimeBetweenDisplays", - "required": false - }, - { - "type": "cwsprChatTimeToFirstChunk" - }, - { - "type": "cwsprChatTimeToFirstDisplay", - "required": false - }, - { - "type": "cwsprChatTimeToFirstUsableChunk", - "required": false - }, - { - "type": "cwsprChatTriggerInteraction" - }, - { - "type": "cwsprChatUserInputContextLength", - "required": false - }, - { - "type": "cwsprChatUserInputContextTruncatedLength", - "required": false - }, - { - "type": "cwsprChatUserIntent", - "required": false - }, - { - "type": "cwsprChatWorkspaceContextLength", - "required": false - }, - { - "type": "cwsprChatWorkspaceContextTruncatedLength", - "required": false - } - ] } ] }