Skip to content

Revert "feat: enable inline project context in suggestion requests" #991

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import {
import { CodeDiffTracker } from './codeDiffTracker'
import { TelemetryService } from '../../shared/telemetry/telemetryService'
import { initBaseTestServiceManager, TestAmazonQServiceManager } from '../../shared/amazonQServiceManager/testUtils'
import { LocalProjectContextController } from '../../shared/localProjectContextController'

const updateConfiguration = async (
features: TestFeatures,
Expand Down Expand Up @@ -547,10 +546,6 @@ describe('CodeWhisperer Server', () => {
})
)

sandbox.stub(LocalProjectContextController, 'getInstance').returns({
queryInlineProjectContext: sandbox.stub().resolves([]),
} as unknown as LocalProjectContextController)

// Initialize the features, but don't start server yet
TestAmazonQServiceManager.resetInstance()
const test_features = new TestFeatures()
Expand Down
7 changes: 0 additions & 7 deletions server/aws-lsp-codewhisperer/src/shared/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@ export enum UserGroup {

export const supplemetalContextFetchingTimeoutMsg = 'Amazon Q supplemental context fetching timeout'

export const supplementalContextMaxTotalLength = 20480

export const supplementalContextTimeoutInMs = 100

export const crossFileContextConfig = {
numberOfChunkToFetch: 60,
topK: 3,
numberOfLinesEachChunk: 10,
maximumTotalLength: 20480,
maxLengthEachChunk: 10240,
maxContextCount: 5,
}

export const utgConfig = {
Expand Down
4 changes: 1 addition & 3 deletions server/aws-lsp-codewhisperer/src/shared/models/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ export type UtgStrategy = 'ByName' | 'ByContent'

export type CrossFileStrategy = 'OpenTabs_BM25'

export type ProjectContextStrategy = 'codemap'

export type SupplementalContextStrategy = CrossFileStrategy | ProjectContextStrategy | UtgStrategy | 'Empty'
export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty'

export interface CodeWhispererSupplementalContext {
isUtg: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { CancellationToken } from '@aws/language-server-runtimes/server-interfac
import { SAMPLE_FILE_OF_60_LINES_IN_JAVA, shuffleList } from '../testUtils'
import { supportedLanguageToDialects } from './crossFileContextUtil'
import { crossFileContextConfig } from '../models/constants'
import { LocalProjectContextController } from '../localProjectContextController'

describe('crossFileContextUtil', function () {
const fakeCancellationToken: CancellationToken = {
Expand Down Expand Up @@ -247,89 +246,4 @@ describe('crossFileContextUtil', function () {
assert.strictEqual(chunks.length, 6)
})
})
describe('codemapContext', () => {
let sandbox: sinon.SinonSandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
})
afterEach(() => {
sandbox.restore()
})

it('should return Empty strategy when no contexts are found', async () => {
const document = TextDocument.create(
'file:///testfile.java',
'java',
1,
'line_1\nline_2\nline_3\nline_4\nline_5\nline_6\nline_7'
)
sandbox.stub(LocalProjectContextController, 'getInstance').returns({
queryInlineProjectContext: sandbox.stub().resolves([]),
} as unknown as LocalProjectContextController)

const result = await crossFile.codemapContext(
document,
{ line: 0, character: 0 },
features.workspace,
fakeCancellationToken
)
assert.deepStrictEqual(result, {
supplementalContextItems: [],
strategy: 'Empty',
})
})
it('should return codemap strategy when project context exists', async () => {
const document = TextDocument.create(
'file:///testfile.java',
'java',
1,
'line_1\nline_2\nline_3\nline_4\nline_5\nline_6\nline_7'
)

sandbox.stub(LocalProjectContextController, 'getInstance').returns({
queryInlineProjectContext: sandbox
.stub()
.resolves([{ content: 'someOtherContet', filePath: '/path/', score: 29.879 }]),
} as unknown as LocalProjectContextController)

const result = await crossFile.codemapContext(
document,
{ line: 0, character: 0 },
features.workspace,
fakeCancellationToken
)
assert.deepStrictEqual(result, {
supplementalContextItems: [{ content: 'someOtherContet', filePath: '/path/', score: 29.879 }],
strategy: 'codemap',
})
})
it('should return OpenTabs_BM25 strategy when only open tabs context exists', async () => {
const document = TextDocument.create(
'file:///testfile.java',
'java',
1,
'line_1\nline_2\nline_3\nline_4\nline_5\nline_6\nline_7'
)
// Open files for openTabsContext to find
features.openDocument(TextDocument.create('file:///OpenFile.java', 'java', 1, 'sample-content'))
// Return [] for fetchProjectContext
sandbox.stub(LocalProjectContextController, 'getInstance').returns({
queryInlineProjectContext: sandbox.stub().resolves([]),
} as unknown as LocalProjectContextController)

const result = await crossFile.codemapContext(
document,
{ line: 0, character: 0 },
features.workspace,
fakeCancellationToken
)
assert.deepStrictEqual(result, {
supplementalContextItems: [
{ content: 'sample-content', filePath: '/OpenFile.java', score: 0 },
{ content: 'sample-content', filePath: '/OpenFile.java', score: 0 },
],
strategy: 'OpenTabs_BM25',
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,10 @@
import * as path from 'path'
import * as nodeUrl from 'url'
import { BM25Document, BM25Okapi } from './rankBm25'
import {
crossFileContextConfig,
supplementalContextMaxTotalLength,
supplementalContextTimeoutInMs,
} from '../models/constants'
import { crossFileContextConfig } from '../models/constants'
import { isTestFile } from './codeParsingUtil'
import { fileSystemUtils } from '@aws/lsp-core'
import {
CodeWhispererSupplementalContext,
CodeWhispererSupplementalContextItem,
SupplementalContextStrategy,
} from '../models/model'
import { CodeWhispererSupplementalContext, CodeWhispererSupplementalContextItem } from '../models/model'
import {
CancellationToken,
Position,
Expand All @@ -25,10 +17,6 @@ import {
Range,
} from '@aws/language-server-runtimes/server-interface'
import { CancellationError } from './supplementalContextUtil'
import { LocalProjectContextController } from '../../shared/localProjectContextController'
import { QueryInlineProjectContextRequestV2 } from 'local-indexing'
import { URI } from 'vscode-uri'
import { waitUntil } from '@aws/lsp-core/out/util/timeoutUtils'

type CrossFileSupportedLanguage =
| 'java'
Expand Down Expand Up @@ -67,97 +55,17 @@ export async function fetchSupplementalContextForSrc(
workspace: Workspace,
cancellationToken: CancellationToken
): Promise<Pick<CodeWhispererSupplementalContext, 'supplementalContextItems' | 'strategy'> | undefined> {
const supplementalContextConfig = getSupplementalContextConfig(document.languageId)

if (supplementalContextConfig === undefined) {
return supplementalContextConfig
}
//TODO: add logic for other strategies once available
if (supplementalContextConfig === 'codemap') {
return await codemapContext(document, position, workspace, cancellationToken)
}
}

export async function codemapContext(
document: TextDocument,
position: Position,
workspace: Workspace,
cancellationToken: CancellationToken
): Promise<Pick<CodeWhispererSupplementalContext, 'supplementalContextItems' | 'strategy'> | undefined> {
let strategy: SupplementalContextStrategy = 'Empty'

const openTabsContextPromise = waitUntil(
async function () {
return await fetchOpenTabsContext(document, position, workspace, cancellationToken)
},
{ timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false }
)

const projectContextPromise = waitUntil(
async function () {
return await fetchProjectContext(document, position, 'codemap')
},
{ timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false }
)

const supContext: CodeWhispererSupplementalContextItem[] = []
const [openTabsContext, projectContext] = await Promise.all([openTabsContextPromise, projectContextPromise])

function addToResult(items: CodeWhispererSupplementalContextItem[]) {
for (const item of items) {
const curLen = supContext.reduce((acc, i) => acc + i.content.length, 0)
if (curLen + item.content.length < supplementalContextMaxTotalLength) {
supContext.push(item)
}
}
}

if (projectContext && projectContext.length > 0) {
addToResult(projectContext)
strategy = 'codemap'
}

if (openTabsContext && openTabsContext.length > 0) {
addToResult(openTabsContext)
if (strategy === 'Empty') {
strategy = 'OpenTabs_BM25'
}
}

return {
supplementalContextItems: supContext ?? [],
strategy,
const shouldProceed = shouldFetchCrossFileContext(document.languageId)

if (!shouldProceed) {
return shouldProceed === undefined
? undefined
: {
supplementalContextItems: [],
strategy: 'Empty',
}
}
}

export async function fetchProjectContext(
document: TextDocument,
position: Position,
target: 'default' | 'codemap' | 'bm25'
): Promise<CodeWhispererSupplementalContextItem[]> {
const inputChunk: Chunk = getInputChunk(document, position, crossFileContextConfig.numberOfLinesEachChunk)
const fsPath = URI.parse(document.uri).fsPath
let controller: LocalProjectContextController
const inlineProjectContextRequest: QueryInlineProjectContextRequestV2 = {
query: inputChunk.content,
filePath: fsPath,
target,
}

try {
controller = LocalProjectContextController.getInstance()
} catch (e) {
return []
}
return controller.queryInlineProjectContext(inlineProjectContextRequest)
}

export async function fetchOpenTabsContext(
document: TextDocument,
position: Position,
workspace: Workspace,
cancellationToken: CancellationToken
): Promise<CodeWhispererSupplementalContextItem[]> {
const codeChunksCalculated = crossFileContextConfig.numberOfChunkToFetch

// Step 1: Get relevant cross files to refer
Expand Down Expand Up @@ -190,14 +98,8 @@ export async function fetchOpenTabsContext(

// Step 4: Transform best chunks to supplemental contexts
const supplementalContexts: CodeWhispererSupplementalContextItem[] = []
let totalLength = 0
for (const chunk of bestChunks) {
throwIfCancelled(cancellationToken)
totalLength += chunk.nextContent.length

if (totalLength > crossFileContextConfig.maximumTotalLength) {
break
}

supplementalContexts.push({
filePath: chunk.fileName,
Expand All @@ -207,7 +109,10 @@ export async function fetchOpenTabsContext(
}

// DO NOT send code chunk with empty content
return supplementalContexts.filter(item => item.content.trim().length !== 0)
return {
supplementalContextItems: supplementalContexts.filter(item => item.content.trim().length !== 0),
strategy: 'OpenTabs_BM25',
}
}

function findBestKChunkMatches(chunkInput: Chunk, chunkReferences: Chunk[], k: number): Chunk[] {
Expand Down Expand Up @@ -252,11 +157,12 @@ function getInputChunk(document: TextDocument, cursorPosition: Position, chunkSi
* @returns specifically returning undefined if the langueage is not supported,
* otherwise true/false depending on if the language is fully supported or not belonging to the user group
*/
function getSupplementalContextConfig(
languageId: TextDocument['languageId'],
_userGroup?: any
): SupplementalContextStrategy | undefined {
return isCrossFileSupported(languageId) ? 'codemap' : undefined
function shouldFetchCrossFileContext(languageId: TextDocument['languageId'], _userGroup?: any): boolean | undefined {
if (!isCrossFileSupported(languageId)) {
return undefined
}

return true
}

/**
Expand Down
Loading
Loading