diff --git a/packages/amazonq/.changes/next-release/Bug Fix-9f5467f7-da28-455c-9471-a5799055ddb7.json b/packages/amazonq/.changes/next-release/Bug Fix-9f5467f7-da28-455c-9471-a5799055ddb7.json new file mode 100644 index 00000000000..5491cc21aeb --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-9f5467f7-da28-455c-9471-a5799055ddb7.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Fix empty chunks being sent to service and get validationException" +} diff --git a/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts index f18db3cc38f..112fe45a16d 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode' import * as sinon from 'sinon' import * as crossFile from 'aws-core-vscode/codewhisperer' import { aStringWithLineCount, createMockTextEditor } from 'aws-core-vscode/test' -import { crossFileContextConfig } from 'aws-core-vscode/codewhisperer' +import { FeatureConfigProvider, crossFileContextConfig } from 'aws-core-vscode/codewhisperer' import { assertTabCount, closeAllEditors, @@ -30,12 +30,17 @@ describe('crossFileContextUtil', function () { let mockEditor: vscode.TextEditor + afterEach(function () { + sinon.restore() + }) + describe('fetchSupplementalContextForSrc', function () { beforeEach(async function () { tempFolder = (await createTestWorkspaceFolder()).uri.fsPath }) - it('should fetch 3 chunks and each chunk should contains 50 lines', async function () { + it('opentabs context should fetch 3 chunks and each chunk should contains 50 lines', async function () { + sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false) await toTextEditor(aStringWithLineCount(200), 'CrossFile.java', tempFolder, { preview: false }) const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, { preview: false, @@ -207,6 +212,7 @@ describe('crossFileContextUtil', function () { fileExtLists.forEach((fileExt) => { it('should be non empty', async function () { + sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false) const editor = await toTextEditor('content-1', `file-1.${fileExt}`, tempFolder) await toTextEditor('content-2', `file-2.${fileExt}`, tempFolder, { preview: false }) await toTextEditor('content-3', `file-3.${fileExt}`, tempFolder, { preview: false }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts new file mode 100644 index 00000000000..50a5777a024 --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts @@ -0,0 +1,61 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import * as vscode from 'vscode' +import * as sinon from 'sinon' +import * as crossFile from 'aws-core-vscode/codewhisperer' +import { TestFolder } from 'aws-core-vscode/test' +import { FeatureConfigProvider } from 'aws-core-vscode/codewhisperer' +import { toTextEditor } from 'aws-core-vscode/test' + +describe('supplementalContextUtil', function () { + let testFolder: TestFolder + + const fakeCancellationToken: vscode.CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: sinon.spy(), + } + + beforeEach(async function () { + testFolder = await TestFolder.create() + sinon.stub(FeatureConfigProvider.instance, 'isNewProjectContextGroup').alwaysReturned(false) + }) + + afterEach(function () { + sinon.restore() + }) + + describe('fetchSupplementalContext', function () { + describe('openTabsContext', function () { + it('opentabContext should include chunks if non empty', async function () { + await toTextEditor('class Foo', 'Foo.java', testFolder.path, { preview: false }) + await toTextEditor('class Bar', 'Bar.java', testFolder.path, { preview: false }) + await toTextEditor('class Baz', 'Baz.java', testFolder.path, { preview: false }) + + const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, { + preview: false, + }) + + const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken) + assert.ok(actual?.supplementalContextItems.length === 3) + }) + + it('opentabsContext should filter out empty chunks', async function () { + // open 3 files as supplemental context candidate files but none of them have contents + await toTextEditor('', 'Foo.java', testFolder.path, { preview: false }) + await toTextEditor('', 'Bar.java', testFolder.path, { preview: false }) + await toTextEditor('', 'Baz.java', testFolder.path, { preview: false }) + + const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, { + preview: false, + }) + + const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken) + assert.ok(actual?.supplementalContextItems.length === 0) + }) + }) + }) +}) diff --git a/packages/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index 72b5d2874eb..1176eaea59e 100644 --- a/packages/core/src/codewhisperer/util/editorContext.ts +++ b/packages/core/src/codewhisperer/util/editorContext.ts @@ -17,6 +17,7 @@ import { selectFrom } from '../../shared/utilities/tsUtils' import { checkLeftContextKeywordsForJson } from './commonUtil' import { CodeWhispererSupplementalContext } from '../models/model' import { getOptOutPreference } from '../../shared/telemetry/util' +import { indent } from '../../shared' let tabSize: number = getTabSizeSetting() @@ -202,18 +203,26 @@ function logSupplementalContext(supplementalContext: CodeWhispererSupplementalCo return } - let logString = `CodeWhispererSupplementalContext: - isUtg: ${supplementalContext.isUtg}, - isProcessTimeout: ${supplementalContext.isProcessTimeout}, - contentsLength: ${supplementalContext.contentsLength}, - latency: ${supplementalContext.latency}, -` + let logString = indent( + `CodeWhispererSupplementalContext: + isUtg: ${supplementalContext.isUtg}, + isProcessTimeout: ${supplementalContext.isProcessTimeout}, + contentsLength: ${supplementalContext.contentsLength}, + latency: ${supplementalContext.latency} + strategy: ${supplementalContext.strategy}`, + 4, + true + ).trimStart() + supplementalContext.supplementalContextItems.forEach((context, index) => { - logString += `Chunk ${index}: - Path: ${context.filePath} - Content: ${index}:${context.content} - Score: ${context.score} - -----------------------------------------------` + logString += indent(`\nChunk ${index}:\n`, 4, true) + logString += indent( + `Path: ${context.filePath} + Length: ${context.content.length} + Score: ${context.score}`, + 8, + true + ) }) getLogger().debug(logString) diff --git a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts index 1352f3ae8c8..8ef6b9841cd 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts @@ -152,7 +152,7 @@ export async function fetchSupplementalContextForSrcV1( // DO NOT send code chunk with empty content getLogger().debug(`CodeWhisperer finished fetching crossfile context out of ${relevantCrossFilePaths.length} files`) return { - supplementalContextItems: supplementalContexts.filter((item) => item.content.trim().length !== 0), + supplementalContextItems: supplementalContexts, strategy: 'OpenTabs_BM25', } } diff --git a/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts b/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts index 30ccbaf7f60..5cb7c2bfb83 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts @@ -39,7 +39,9 @@ export async function fetchSupplementalContext( return { isUtg: isUtg, isProcessTimeout: false, - supplementalContextItems: value.supplementalContextItems, + supplementalContextItems: value.supplementalContextItems.filter( + (item) => item.content.trim().length !== 0 + ), contentsLength: value.supplementalContextItems.reduce((acc, curr) => acc + curr.content.length, 0), latency: performance.now() - timesBeforeFetching, strategy: value.strategy,