Skip to content

Commit 3fb8a9e

Browse files
authored
Merge branch 'aws:master' into master
2 parents 8505d7b + bd37885 commit 3fb8a9e

File tree

13 files changed

+454
-156
lines changed

13 files changed

+454
-156
lines changed

package-lock.json

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Fix inline completion failure due to context length exceeding the threshold"
4+
}

packages/amazonq/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"Codewhisperer",
3333
"AI",
3434
"Assistant",
35-
"Chatbot"
35+
"Chatbot",
36+
"Q Developer"
3637
],
3738
"preview": false,
3839
"qna": "https://github.com/aws/aws-toolkit-vscode/issues",

packages/amazonq/scripts/build/copyFiles.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,6 @@ const tasks: CopyTask[] = [
5656
destination: 'vue/',
5757
},
5858

59-
// Mynah
60-
{
61-
target: path.join(
62-
'../../node_modules',
63-
'@aws',
64-
'fully-qualified-names',
65-
'node',
66-
'aws_fully_qualified_names_bg.wasm'
67-
),
68-
destination: path.join('src', 'aws_fully_qualified_names_bg.wasm'),
69-
},
7059
{
7160
target: path.join('../../node_modules', 'web-tree-sitter', 'tree-sitter.wasm'),
7261
destination: path.join('src', 'tree-sitter.wasm'),

packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import assert from 'assert'
77
import * as FakeTimers from '@sinonjs/fake-timers'
88
import * as vscode from 'vscode'
99
import * as sinon from 'sinon'
10+
import * as os from 'os'
1011
import * as crossFile from 'aws-core-vscode/codewhisperer'
1112
import { TestFolder, assertTabCount, installFakeClock } from 'aws-core-vscode/test'
12-
import { FeatureConfigProvider } from 'aws-core-vscode/codewhisperer'
13+
import { CodeWhispererSupplementalContext, FeatureConfigProvider } from 'aws-core-vscode/codewhisperer'
1314
import { toTextEditor } from 'aws-core-vscode/test'
1415
import { LspController } from 'aws-core-vscode/amazonq'
1516

17+
const newLine = os.EOL
18+
1619
describe('supplementalContextUtil', function () {
1720
let testFolder: TestFolder
1821
let clock: FakeTimers.InstalledClock
@@ -83,4 +86,180 @@ describe('supplementalContextUtil', function () {
8386
})
8487
})
8588
})
89+
90+
describe('truncation', function () {
91+
it('truncate context should do nothing if everything fits in constraint', function () {
92+
const chunkA: crossFile.CodeWhispererSupplementalContextItem = {
93+
content: 'a',
94+
filePath: 'a.java',
95+
score: 0,
96+
}
97+
const chunkB: crossFile.CodeWhispererSupplementalContextItem = {
98+
content: 'b',
99+
filePath: 'b.java',
100+
score: 1,
101+
}
102+
const chunks = [chunkA, chunkB]
103+
104+
const supplementalContext: CodeWhispererSupplementalContext = {
105+
isUtg: false,
106+
isProcessTimeout: false,
107+
supplementalContextItems: chunks,
108+
contentsLength: 25000,
109+
latency: 0,
110+
strategy: 'codemap',
111+
}
112+
113+
const actual = crossFile.truncateSuppelementalContext(supplementalContext)
114+
assert.strictEqual(actual.supplementalContextItems.length, 2)
115+
assert.strictEqual(actual.supplementalContextItems[0].content, 'a')
116+
assert.strictEqual(actual.supplementalContextItems[1].content, 'b')
117+
})
118+
119+
it('truncateLineByLine should drop the last line if max length is greater than threshold', function () {
120+
const input =
121+
repeatString('a', 11) +
122+
newLine +
123+
repeatString('b', 11) +
124+
newLine +
125+
repeatString('c', 11) +
126+
newLine +
127+
repeatString('d', 11) +
128+
newLine +
129+
repeatString('e', 11)
130+
131+
assert.ok(input.length > 50)
132+
const actual = crossFile.truncateLineByLine(input, 50)
133+
assert.ok(actual.length <= 50)
134+
135+
const input2 = repeatString(`b${newLine}`, 10)
136+
const actual2 = crossFile.truncateLineByLine(input2, 8)
137+
assert.ok(actual2.length <= 8)
138+
})
139+
140+
it('truncation context should make context length per item lte 10240 cap', function () {
141+
const chunkA: crossFile.CodeWhispererSupplementalContextItem = {
142+
content: repeatString(`a${newLine}`, 4000),
143+
filePath: 'a.java',
144+
score: 0,
145+
}
146+
const chunkB: crossFile.CodeWhispererSupplementalContextItem = {
147+
content: repeatString(`b${newLine}`, 6000),
148+
filePath: 'b.java',
149+
score: 1,
150+
}
151+
const chunkC: crossFile.CodeWhispererSupplementalContextItem = {
152+
content: repeatString(`c${newLine}`, 1000),
153+
filePath: 'c.java',
154+
score: 2,
155+
}
156+
const chunkD: crossFile.CodeWhispererSupplementalContextItem = {
157+
content: repeatString(`d${newLine}`, 1500),
158+
filePath: 'd.java',
159+
score: 3,
160+
}
161+
162+
assert.ok(
163+
chunkA.content.length + chunkB.content.length + chunkC.content.length + chunkD.content.length > 20480
164+
)
165+
166+
const supplementalContext: CodeWhispererSupplementalContext = {
167+
isUtg: false,
168+
isProcessTimeout: false,
169+
supplementalContextItems: [chunkA, chunkB, chunkC, chunkD],
170+
contentsLength: 25000,
171+
latency: 0,
172+
strategy: 'codemap',
173+
}
174+
175+
const actual = crossFile.truncateSuppelementalContext(supplementalContext)
176+
assert.strictEqual(actual.supplementalContextItems.length, 3)
177+
assert.ok(actual.contentsLength <= 20480)
178+
assert.strictEqual(actual.strategy, 'codemap')
179+
})
180+
181+
it('truncate context should make context items lte 5', function () {
182+
const chunkA: crossFile.CodeWhispererSupplementalContextItem = {
183+
content: 'a',
184+
filePath: 'a.java',
185+
score: 0,
186+
}
187+
const chunkB: crossFile.CodeWhispererSupplementalContextItem = {
188+
content: 'b',
189+
filePath: 'b.java',
190+
score: 1,
191+
}
192+
const chunkC: crossFile.CodeWhispererSupplementalContextItem = {
193+
content: 'c',
194+
filePath: 'c.java',
195+
score: 2,
196+
}
197+
const chunkD: crossFile.CodeWhispererSupplementalContextItem = {
198+
content: 'd',
199+
filePath: 'd.java',
200+
score: 3,
201+
}
202+
const chunkE: crossFile.CodeWhispererSupplementalContextItem = {
203+
content: 'e',
204+
filePath: 'e.java',
205+
score: 4,
206+
}
207+
const chunkF: crossFile.CodeWhispererSupplementalContextItem = {
208+
content: 'f',
209+
filePath: 'f.java',
210+
score: 5,
211+
}
212+
const chunkG: crossFile.CodeWhispererSupplementalContextItem = {
213+
content: 'g',
214+
filePath: 'g.java',
215+
score: 6,
216+
}
217+
const chunks = [chunkA, chunkB, chunkC, chunkD, chunkE, chunkF, chunkG]
218+
219+
assert.strictEqual(chunks.length, 7)
220+
221+
const supplementalContext: CodeWhispererSupplementalContext = {
222+
isUtg: false,
223+
isProcessTimeout: false,
224+
supplementalContextItems: chunks,
225+
contentsLength: 25000,
226+
latency: 0,
227+
strategy: 'codemap',
228+
}
229+
230+
const actual = crossFile.truncateSuppelementalContext(supplementalContext)
231+
assert.strictEqual(actual.supplementalContextItems.length, 5)
232+
})
233+
234+
describe('truncate line by line', function () {
235+
it('should return empty if empty string is provided', function () {
236+
const input = ''
237+
const actual = crossFile.truncateLineByLine(input, 50)
238+
assert.strictEqual(actual, '')
239+
})
240+
241+
it('should return empty if 0 max length is provided', function () {
242+
const input = 'aaaaa'
243+
const actual = crossFile.truncateLineByLine(input, 0)
244+
assert.strictEqual(actual, '')
245+
})
246+
247+
it('should flip the value if negative max length is provided', function () {
248+
const input = `aaaaa${newLine}bbbbb`
249+
const actual = crossFile.truncateLineByLine(input, -6)
250+
const expected = crossFile.truncateLineByLine(input, 6)
251+
assert.strictEqual(actual, expected)
252+
assert.strictEqual(actual, 'aaaaa')
253+
})
254+
})
255+
})
86256
})
257+
258+
function repeatString(s: string, n: number): string {
259+
let output = ''
260+
for (let i = 0; i < n; i++) {
261+
output += s
262+
}
263+
264+
return output
265+
}

packages/core/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,6 @@
440440
},
441441
"devDependencies": {
442442
"@aws-sdk/types": "^3.13.1",
443-
"@aws/fully-qualified-names": "^2.1.4",
444443
"@cspotcode/source-map-support": "^0.8.1",
445444
"@sinonjs/fake-timers": "^10.0.2",
446445
"@types/adm-zip": "^0.4.34",
@@ -497,6 +496,7 @@
497496
"@amzn/amazon-q-developer-streaming-client": "file:../../src.gen/@amzn/amazon-q-developer-streaming-client",
498497
"@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming",
499498
"@aws-sdk/client-api-gateway": "<3.696.0",
499+
"@aws-sdk/client-cloudcontrol": "<3.696.0",
500500
"@aws-sdk/client-cloudformation": "<3.696.0",
501501
"@aws-sdk/client-cloudwatch-logs": "<3.696.0",
502502
"@aws-sdk/client-codecatalyst": "<3.696.0",
@@ -510,7 +510,6 @@
510510
"@aws-sdk/client-ssm": "<3.696.0",
511511
"@aws-sdk/client-sso": "<3.696.0",
512512
"@aws-sdk/client-sso-oidc": "<3.696.0",
513-
"@aws-sdk/client-cloudcontrol": "<3.696.0",
514513
"@aws-sdk/credential-provider-env": "<3.696.0",
515514
"@aws-sdk/credential-provider-process": "<3.696.0",
516515
"@aws-sdk/credential-provider-sso": "<3.696.0",

packages/core/src/amazonq/util/files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export async function prepareRepoData(
8787
}
8888

8989
const files = await collectFiles(repoRootPaths, workspaceFolders, {
90-
maxSizeBytes: maxRepoSizeBytes,
90+
maxTotalSizeBytes: maxRepoSizeBytes,
9191
excludeByGitIgnore: true,
9292
excludePatterns: excludePatterns,
9393
filterFn: filterFn,

packages/core/src/codewhisperer/models/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,8 @@ export const crossFileContextConfig = {
856856
topK: 3,
857857
numberOfLinesEachChunk: 50,
858858
maximumTotalLength: 20480,
859+
maxLengthEachChunk: 10240,
860+
maxContextCount: 5,
859861
}
860862

861863
export const utgConfig = {

packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { CancellationError } from '../../../shared/utilities/timeoutUtils'
1111
import { ToolkitError } from '../../../shared/errors'
1212
import { getLogger } from '../../../shared/logger/logger'
1313
import { CodeWhispererSupplementalContext } from '../../models/model'
14+
import * as os from 'os'
15+
import { crossFileContextConfig } from '../../models/constants'
1416

1517
export async function fetchSupplementalContext(
1618
editor: vscode.TextEditor,
@@ -36,7 +38,7 @@ export async function fetchSupplementalContext(
3638
return supplementalContextPromise
3739
.then((value) => {
3840
if (value) {
39-
return {
41+
const resBeforeTruncation = {
4042
isUtg: isUtg,
4143
isProcessTimeout: false,
4244
supplementalContextItems: value.supplementalContextItems.filter(
@@ -46,6 +48,8 @@ export async function fetchSupplementalContext(
4648
latency: performance.now() - timesBeforeFetching,
4749
strategy: value.strategy,
4850
}
51+
52+
return truncateSuppelementalContext(resBeforeTruncation)
4953
} else {
5054
return undefined
5155
}
@@ -68,3 +72,66 @@ export async function fetchSupplementalContext(
6872
}
6973
})
7074
}
75+
76+
/**
77+
* Requirement
78+
* - Maximum 5 supplemental context.
79+
* - Each chunk can't exceed 10240 characters
80+
* - Sum of all chunks can't exceed 20480 characters
81+
*/
82+
export function truncateSuppelementalContext(
83+
context: CodeWhispererSupplementalContext
84+
): CodeWhispererSupplementalContext {
85+
let c = context.supplementalContextItems.map((item) => {
86+
if (item.content.length > crossFileContextConfig.maxLengthEachChunk) {
87+
return {
88+
...item,
89+
content: truncateLineByLine(item.content, crossFileContextConfig.maxLengthEachChunk),
90+
}
91+
} else {
92+
return item
93+
}
94+
})
95+
96+
if (c.length > crossFileContextConfig.maxContextCount) {
97+
c = c.slice(0, crossFileContextConfig.maxContextCount)
98+
}
99+
100+
let curTotalLength = c.reduce((acc, cur) => {
101+
return acc + cur.content.length
102+
}, 0)
103+
while (curTotalLength >= 20480 && c.length - 1 >= 0) {
104+
const last = c[c.length - 1]
105+
c = c.slice(0, -1)
106+
curTotalLength -= last.content.length
107+
}
108+
109+
return {
110+
...context,
111+
supplementalContextItems: c,
112+
contentsLength: curTotalLength,
113+
}
114+
}
115+
116+
export function truncateLineByLine(input: string, l: number): string {
117+
const maxLength = l > 0 ? l : -1 * l
118+
if (input.length === 0) {
119+
return ''
120+
}
121+
122+
const shouldAddNewLineBack = input.endsWith(os.EOL)
123+
let lines = input.trim().split(os.EOL)
124+
let curLen = input.length
125+
while (curLen > maxLength && lines.length - 1 >= 0) {
126+
const last = lines[lines.length - 1]
127+
lines = lines.slice(0, -1)
128+
curLen -= last.length + 1
129+
}
130+
131+
const r = lines.join(os.EOL)
132+
if (shouldAddNewLineBack) {
133+
return r + os.EOL
134+
} else {
135+
return r
136+
}
137+
}

packages/core/src/codewhisperer/util/zipUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ export class ZipUtil {
420420
)
421421
: vscode.workspace.workspaceFolders) as CurrentWsFolders,
422422
{
423-
maxSizeBytes: this.getProjectScanPayloadSizeLimitInBytes(),
423+
maxTotalSizeBytes: this.getProjectScanPayloadSizeLimitInBytes(),
424424
excludePatterns:
425425
useCase === FeatureUseCase.TEST_GENERATION
426426
? [...CodeWhispererConstants.testGenExcludePatterns, ...defaultExcludePatterns]

0 commit comments

Comments
 (0)