-
Notifications
You must be signed in to change notification settings - Fork 747
feat(nep): Data Instrumentation #7109
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
jpinkney-aws
merged 29 commits into
aws:master
from
tomcat323:NEP/DataInstrumentationLaunch
May 5, 2025
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
808d95e
Added Prediction folder, integrated with API, status bar visualier
tomcat323 ced1ec6
refactor supplemental context generation into diffGenerator
tomcat323 475c5dd
clean up pakcage.json
tomcat323 440c44e
update supplemental context logic according to science feedback
tomcat323 ea601c9
not include debug webview in release
tomcat323 2bfa873
refactor config to constants.ts
tomcat323 5aa4c51
added tests for prediction tracker
tomcat323 9cdb6f5
clean up
tomcat323 a371128
remove tracker dispose in activation
tomcat323 dabf670
remove unused imports
tomcat323 9476765
refactored filePath to getter functions
tomcat323 d233c0c
refactor load from storage, use reduce in getTotalCount
tomcat323 fa6b40b
remove predictionTypes from request
tomcat323 b935964
enforce supplemental context char limit
tomcat323 25cc9f9
address comments, deprecate fs usage, use in memory content tracking
tomcat323 18603fa
clean up logging, fix predictionTracker tests
tomcat323 6c3d6ab
Merge branch 'master' into NEP/DataInstrumentationLaunch
tomcat323 c683cb8
refactor paths for windows test fail
tomcat323 7d25d3f
fix uri path leading seperator
tomcat323 0446ccb
add more try-catch for error handling
tomcat323 1a9b305
remove unused maxfiles constant
tomcat323 4250b65
add tests for diff context generation
tomcat323 ed30502
Merge branch 'master' into NEP/DataInstrumentationLaunch
tomcat323 6261398
resolve merge conflict in settings
tomcat323 adfe12e
renamed files, removed redundent checks
tomcat323 092d0b7
Rename PredictionTracker.ts to predictionTracker.ts
tomcat323 71384d6
Rename PredictionKeyStrokeHandler.ts to predictionKeyStrokeHandler.ts
tomcat323 399d67d
Rename PredictionTracker.test.ts to predictionTracker.test.ts
tomcat323 d94891d
clean up
tomcat323 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
packages/core/src/codewhisperer/nextEditPrediction/activation.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /*! | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import * as vscode from 'vscode' | ||
| import { PredictionTracker } from './predictionTracker' | ||
| import { PredictionKeyStrokeHandler } from './predictionKeyStrokeHandler' | ||
| import { getLogger } from '../../shared/logger/logger' | ||
| import { ExtContext } from '../../shared/extensions' | ||
|
|
||
| export let predictionTracker: PredictionTracker | undefined | ||
| let keyStrokeHandler: PredictionKeyStrokeHandler | undefined | ||
|
|
||
| export function activateEditTracking(context: ExtContext): void { | ||
| try { | ||
| predictionTracker = new PredictionTracker(context.extensionContext) | ||
|
|
||
| keyStrokeHandler = new PredictionKeyStrokeHandler(predictionTracker) | ||
| context.extensionContext.subscriptions.push( | ||
| vscode.Disposable.from({ | ||
| dispose: () => { | ||
| keyStrokeHandler?.dispose() | ||
| }, | ||
| }) | ||
| ) | ||
|
|
||
| getLogger('nextEditPrediction').debug('Next Edit Prediction activated') | ||
| } catch (error) { | ||
| getLogger('nextEditPrediction').error(`Error in activateEditTracking: ${error}`) | ||
| } | ||
| } | ||
154 changes: 154 additions & 0 deletions
154
packages/core/src/codewhisperer/nextEditPrediction/diffContextGenerator.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| /*! | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import * as diff from 'diff' | ||
| import { getLogger } from '../../shared/logger/logger' | ||
| import * as codewhispererClient from '../client/codewhisperer' | ||
| import { supplementalContextMaxTotalLength, charactersLimit } from '../models/constants' | ||
|
|
||
| const logger = getLogger('nextEditPrediction') | ||
|
|
||
| /** | ||
| * Generates a unified diff format between old and new file contents | ||
| */ | ||
| function generateUnifiedDiffWithTimestamps( | ||
| oldFilePath: string, | ||
| newFilePath: string, | ||
| oldContent: string, | ||
| newContent: string, | ||
| oldTimestamp: number, | ||
| newTimestamp: number, | ||
| contextSize: number = 3 | ||
| ): string { | ||
| const patchResult = diff.createTwoFilesPatch( | ||
| oldFilePath, | ||
| newFilePath, | ||
| oldContent, | ||
| newContent, | ||
| String(oldTimestamp), | ||
| String(newTimestamp), | ||
| { context: contextSize } | ||
| ) | ||
|
|
||
| // Remove unused headers | ||
| const lines = patchResult.split('\n') | ||
| if (lines.length >= 2 && lines[0].startsWith('Index:')) { | ||
| lines.splice(0, 2) | ||
| return lines.join('\n') | ||
| } | ||
|
|
||
| return patchResult | ||
| } | ||
|
|
||
| export interface SnapshotContent { | ||
| filePath: string | ||
| content: string | ||
| timestamp: number | ||
| } | ||
|
|
||
| /** | ||
| * Generates supplemental contexts from snapshot contents and current content | ||
| * | ||
| * @param filePath - Path to the file | ||
| * @param currentContent - Current content of the file | ||
| * @param snapshotContents - List of snapshot contents sorted by timestamp (oldest first) | ||
| * @param maxContexts - Maximum number of supplemental contexts to return | ||
| * @returns Array of SupplementalContext objects, T_0 being the snapshot of current file content: | ||
| * U0: udiff of T_0 and T_1 | ||
| * U1: udiff of T_0 and T_2 | ||
| * U2: udiff of T_0 and T_3 | ||
| */ | ||
| export function generateDiffContexts( | ||
| filePath: string, | ||
| currentContent: string, | ||
| snapshotContents: SnapshotContent[], | ||
| maxContexts: number | ||
| ): codewhispererClient.SupplementalContext[] { | ||
| if (snapshotContents.length === 0) { | ||
| return [] | ||
| } | ||
|
|
||
| const supplementalContexts: codewhispererClient.SupplementalContext[] = [] | ||
| const currentTimestamp = Date.now() | ||
|
|
||
| for (let i = snapshotContents.length - 1; i >= 0; i--) { | ||
| const snapshot = snapshotContents[i] | ||
| try { | ||
| const unifiedDiff = generateUnifiedDiffWithTimestamps( | ||
| snapshot.filePath, | ||
| filePath, | ||
| snapshot.content, | ||
| currentContent, | ||
| snapshot.timestamp, | ||
| currentTimestamp | ||
| ) | ||
|
|
||
| supplementalContexts.push({ | ||
| filePath: snapshot.filePath, | ||
| content: unifiedDiff, | ||
| type: 'PreviousEditorState', | ||
| metadata: { | ||
| previousEditorStateMetadata: { | ||
| timeOffset: currentTimestamp - snapshot.timestamp, | ||
| }, | ||
| }, | ||
| }) | ||
| } catch (err) { | ||
| logger.error(`Failed to generate diff: ${err}`) | ||
| } | ||
| } | ||
|
|
||
| const trimmedContext = trimSupplementalContexts(supplementalContexts, maxContexts) | ||
| logger.debug( | ||
| `supplemental contexts: ${trimmedContext.length} contexts, total size: ${trimmedContext.reduce((sum, ctx) => sum + ctx.content.length, 0)} characters` | ||
| ) | ||
| return trimmedContext | ||
| } | ||
|
|
||
| /** | ||
| * Trims the supplementalContexts array to ensure it doesn't exceed the max number | ||
| * of contexts or total character length limit | ||
| * | ||
| * @param supplementalContexts - Array of SupplementalContext objects (already sorted with newest first) | ||
| * @param maxContexts - Maximum number of supplemental contexts allowed | ||
| * @returns Trimmed array of SupplementalContext objects | ||
| */ | ||
| export function trimSupplementalContexts( | ||
| supplementalContexts: codewhispererClient.SupplementalContext[], | ||
| maxContexts: number | ||
| ): codewhispererClient.SupplementalContext[] { | ||
| if (supplementalContexts.length === 0) { | ||
| return supplementalContexts | ||
| } | ||
|
|
||
| // First filter out any individual context that exceeds the character limit | ||
| let result = supplementalContexts.filter((context) => { | ||
| return context.content.length <= charactersLimit | ||
| }) | ||
|
|
||
| // Then limit by max number of contexts | ||
| if (result.length > maxContexts) { | ||
| result = result.slice(0, maxContexts) | ||
| } | ||
|
|
||
| // Lastly enforce total character limit | ||
| let totalLength = 0 | ||
| let i = 0 | ||
|
|
||
| while (i < result.length) { | ||
| totalLength += result[i].content.length | ||
| if (totalLength > supplementalContextMaxTotalLength) { | ||
| break | ||
| } | ||
| i++ | ||
| } | ||
|
|
||
| if (i === result.length) { | ||
| return result | ||
| } | ||
|
|
||
| const trimmedContexts = result.slice(0, i) | ||
| return trimmedContexts | ||
| } |
117 changes: 117 additions & 0 deletions
117
packages/core/src/codewhisperer/nextEditPrediction/predictionKeyStrokeHandler.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| /*! | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import * as vscode from 'vscode' | ||
| import { PredictionTracker } from './predictionTracker' | ||
|
|
||
| /** | ||
| * Monitors document changes in the editor and track them for prediction. | ||
| */ | ||
| export class PredictionKeyStrokeHandler { | ||
| private disposables: vscode.Disposable[] = [] | ||
| private tracker: PredictionTracker | ||
| private shadowCopies: Map<string, string> = new Map() | ||
|
|
||
| /** | ||
| * Creates a new PredictionKeyStrokeHandler | ||
| * @param context The extension context | ||
| * @param tracker The prediction tracker instance | ||
| * @param config Configuration options | ||
| */ | ||
| constructor(tracker: PredictionTracker) { | ||
| this.tracker = tracker | ||
|
|
||
| // Initialize shadow copies for currently visible editors when extension starts | ||
| this.initializeVisibleDocuments() | ||
|
|
||
| // Register event handlers | ||
| this.registerVisibleDocumentListener() | ||
| this.registerTextDocumentChangeListener() | ||
| } | ||
|
|
||
| /** | ||
| * Initializes shadow copies for all currently visible text editors | ||
| */ | ||
| private initializeVisibleDocuments(): void { | ||
| const editors = vscode.window.visibleTextEditors | ||
|
|
||
| for (const editor of editors) { | ||
| if (editor.document.uri.scheme === 'file') { | ||
| this.updateShadowCopy(editor.document) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Registers listeners for visibility events to maintain shadow copies of document content | ||
| * Only store and update shadow copies for currently visible editors | ||
| * And remove shadow copies for files that are no longer visible | ||
| * And edits are processed only if a shadow copy exists | ||
| * This avoids the memory problem if hidden files are bulk edited, i.e. with global find/replace | ||
| */ | ||
| private registerVisibleDocumentListener(): void { | ||
| // Track when documents become visible (switched to) | ||
| const visibleDisposable = vscode.window.onDidChangeVisibleTextEditors((editors) => { | ||
| const currentVisibleFiles = new Set<string>() | ||
|
|
||
| for (const editor of editors) { | ||
| if (editor.document.uri.scheme === 'file') { | ||
| const filePath = editor.document.uri.fsPath | ||
| currentVisibleFiles.add(filePath) | ||
| this.updateShadowCopy(editor.document) | ||
| } | ||
| } | ||
|
|
||
| for (const filePath of this.shadowCopies.keys()) { | ||
| if (!currentVisibleFiles.has(filePath)) { | ||
| this.shadowCopies.delete(filePath) | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| this.disposables.push(visibleDisposable) | ||
| } | ||
|
|
||
| private updateShadowCopy(document: vscode.TextDocument): void { | ||
| if (document.uri.scheme === 'file') { | ||
| this.shadowCopies.set(document.uri.fsPath, document.getText()) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Registers listener for text document changes to send to tracker | ||
| */ | ||
| private registerTextDocumentChangeListener(): void { | ||
| // Listen for document changes | ||
| const changeDisposable = vscode.workspace.onDidChangeTextDocument(async (event) => { | ||
| const filePath = event.document.uri.fsPath | ||
| const prevContent = this.shadowCopies.get(filePath) | ||
|
|
||
| // Skip if there are no content changes or if the file is not visible | ||
| if ( | ||
| event.contentChanges.length === 0 || | ||
| event.document.uri.scheme !== 'file' || | ||
| prevContent === undefined | ||
| ) { | ||
| return | ||
| } | ||
|
|
||
| await this.tracker.processEdit(event.document, prevContent) | ||
| this.updateShadowCopy(event.document) | ||
| }) | ||
|
|
||
| this.disposables.push(changeDisposable) | ||
| } | ||
|
|
||
| /** | ||
| * Disposes of all resources used by this handler | ||
| */ | ||
| public dispose(): void { | ||
| for (const disposable of this.disposables) { | ||
| disposable.dispose() | ||
| } | ||
| this.disposables = [] | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.