From 7f36a2d6064cf934b7dda1344b9c43cac710e4e0 Mon Sep 17 00:00:00 2001
From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com>
Date: Thu, 24 Jul 2025 13:43:54 -0700
Subject: [PATCH 01/18] config(amazonq): disable inline tutorial since it's
taking ~250ms for all users no matter it's shown or not (#7722)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
…
## Problem
by design, the code path should only be applied to "new" users, however
currently it's taking 250ms for all users no matter the UI is displayed
or not.
## Solution
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/src/app/inline/completion.ts | 30 +++++------
.../src/app/inline/recommendationService.ts | 5 +-
.../tutorials/inlineTutorialAnnotation.ts | 51 ++++++++-----------
3 files changed, 40 insertions(+), 46 deletions(-)
diff --git a/packages/amazonq/src/app/inline/completion.ts b/packages/amazonq/src/app/inline/completion.ts
index be49a0654cc..9aaf40c4c43 100644
--- a/packages/amazonq/src/app/inline/completion.ts
+++ b/packages/amazonq/src/app/inline/completion.ts
@@ -198,7 +198,7 @@ export class InlineCompletionManager implements Disposable {
}
export class AmazonQInlineCompletionItemProvider implements InlineCompletionItemProvider {
- private logger = getLogger('nextEditPrediction')
+ private logger = getLogger()
constructor(
private readonly languageClient: LanguageClient,
private readonly recommendationService: RecommendationService,
@@ -299,7 +299,8 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
}
// re-use previous suggestions as long as new typed prefix matches
if (prevItemMatchingPrefix.length > 0) {
- getLogger().debug(`Re-using suggestions that match user typed characters`)
+ logstr += `- not call LSP and reuse previous suggestions that match user typed characters
+ - duration between trigger to completion suggestion is displayed ${performance.now() - t0}`
return prevItemMatchingPrefix
}
getLogger().debug(`Auto rejecting suggestions from previous session`)
@@ -318,7 +319,6 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
this.sessionManager.clear()
}
- // TODO: this line will take ~200ms each trigger, need to root cause and maybe better to disable it for now
// tell the tutorial that completions has been triggered
await this.inlineTutorialAnnotation.triggered(context.triggerKind)
@@ -346,12 +346,13 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
const t2 = performance.now()
- logstr = logstr += `- number of suggestions: ${items.length}
+ logstr += `- number of suggestions: ${items.length}
- sessionId: ${this.sessionManager.getActiveSession()?.sessionId}
- first suggestion content (next line):
${itemLog}
-- duration since trigger to before sending Flare call: ${t1 - t0}ms
-- duration since trigger to receiving responses from Flare: ${t2 - t0}ms
+- duration between trigger to before sending LSP call: ${t1 - t0}ms
+- duration between trigger to after receiving LSP response: ${t2 - t0}ms
+- duration between before sending LSP call to after receving LSP response: ${t2 - t1}ms
`
const session = this.sessionManager.getActiveSession()
@@ -361,16 +362,13 @@ ${itemLog}
}
if (!session || !items.length || !editor) {
- getLogger().debug(
- `Failed to produce inline suggestion results. Received ${items.length} items from service`
- )
+ logstr += `Failed to produce inline suggestion results. Received ${items.length} items from service`
return []
}
const cursorPosition = document.validatePosition(position)
if (position.isAfter(editor.selection.active)) {
- getLogger().debug(`Cursor moved behind trigger position. Discarding suggestion...`)
const params: LogInlineCompletionSessionResultsParams = {
sessionId: session.sessionId,
completionSessionResult: {
@@ -383,6 +381,7 @@ ${itemLog}
}
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
this.sessionManager.clear()
+ logstr += `- cursor moved behind trigger position. Discarding suggestion...`
return []
}
@@ -410,9 +409,7 @@ ${itemLog}
// Check if Next Edit Prediction feature flag is enabled
if (Experiments.instance.get('amazonqLSPNEP', true)) {
await showEdits(item, editor, session, this.languageClient, this)
- const t3 = performance.now()
- logstr = logstr + `- duration since trigger to NEP suggestion is displayed: ${t3 - t0}ms`
- this.logger.info(logstr)
+ logstr += `- duration between trigger to edits suggestion is displayed: ${performance.now() - t0}ms`
}
return []
}
@@ -438,9 +435,6 @@ ${itemLog}
// report discard if none of suggestions match typeahead
if (itemsMatchingTypeahead.length === 0) {
- getLogger().debug(
- `Suggestion does not match user typeahead from insertion position. Discarding suggestion...`
- )
const params: LogInlineCompletionSessionResultsParams = {
sessionId: session.sessionId,
completionSessionResult: {
@@ -453,17 +447,21 @@ ${itemLog}
}
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
this.sessionManager.clear()
+ logstr += `- suggestion does not match user typeahead from insertion position. Discarding suggestion...`
return []
}
this.sessionManager.updateCodeReferenceAndImports()
// suggestions returned here will be displayed on screen
+ logstr += `- duration between trigger to completion suggestion is displayed: ${performance.now() - t0}ms`
return itemsMatchingTypeahead as InlineCompletionItem[]
} catch (e) {
getLogger('amazonqLsp').error('Failed to provide completion items: %O', e)
+ logstr += `- failed to provide completion items ${(e as Error).message}`
return []
} finally {
vsCodeState.isRecommendationsActive = false
+ this.logger.info(logstr)
}
}
}
diff --git a/packages/amazonq/src/app/inline/recommendationService.ts b/packages/amazonq/src/app/inline/recommendationService.ts
index 1329c68a51c..c7ba5c3b4b7 100644
--- a/packages/amazonq/src/app/inline/recommendationService.ts
+++ b/packages/amazonq/src/app/inline/recommendationService.ts
@@ -92,13 +92,15 @@ export class RecommendationService {
nextToken: request.partialResultToken,
},
})
+ const t0 = performance.now()
const result: InlineCompletionListWithReferences = await languageClient.sendRequest(
inlineCompletionWithReferencesRequestType.method,
request,
token
)
- getLogger().info('Received inline completion response: %O', {
+ getLogger().info('Received inline completion response from LSP: %O', {
sessionId: result.sessionId,
+ latency: performance.now() - t0,
itemCount: result.items?.length || 0,
items: result.items?.map((item) => ({
itemId: item.itemId,
@@ -128,6 +130,7 @@ export class RecommendationService {
const isInlineEdit = result.items.some((item) => item.isInlineEdit)
+ // TODO: question, is it possible that the first request returns empty suggestion but has non-empty next token?
if (result.partialResultToken) {
if (!isInlineEdit) {
// If the suggestion is COMPLETIONS and there are more results to fetch, handle them in the background
diff --git a/packages/amazonq/src/app/inline/tutorials/inlineTutorialAnnotation.ts b/packages/amazonq/src/app/inline/tutorials/inlineTutorialAnnotation.ts
index bd12b1d28dd..ad0807df94c 100644
--- a/packages/amazonq/src/app/inline/tutorials/inlineTutorialAnnotation.ts
+++ b/packages/amazonq/src/app/inline/tutorials/inlineTutorialAnnotation.ts
@@ -5,13 +5,7 @@
import * as vscode from 'vscode'
import * as os from 'os'
-import {
- AnnotationChangeSource,
- AuthUtil,
- inlinehintKey,
- runtimeLanguageContext,
- TelemetryHelper,
-} from 'aws-core-vscode/codewhisperer'
+import { AnnotationChangeSource, AuthUtil, inlinehintKey, runtimeLanguageContext } from 'aws-core-vscode/codewhisperer'
import { editorUtilities, getLogger, globals, setContext, vscodeUtilities } from 'aws-core-vscode/shared'
import { LinesChangeEvent, LineSelection, LineTracker } from '../stateTracker/lineTracker'
import { telemetry } from 'aws-core-vscode/telemetry'
@@ -296,28 +290,27 @@ export class InlineTutorialAnnotation implements vscode.Disposable {
}
async triggered(triggerType: vscode.InlineCompletionTriggerKind): Promise {
- await telemetry.withTraceId(async () => {
- if (!this._isReady) {
- return
- }
-
- if (this._currentState instanceof ManualtriggerState) {
- if (
- triggerType === vscode.InlineCompletionTriggerKind.Invoke &&
- this._currentState.hasManualTrigger === false
- ) {
- this._currentState.hasManualTrigger = true
- }
- if (
- this.sessionManager.getActiveRecommendation().length > 0 &&
- this._currentState.hasValidResponse === false
- ) {
- this._currentState.hasValidResponse = true
- }
- }
-
- await this.refresh(vscode.window.activeTextEditor, 'codewhisperer')
- }, TelemetryHelper.instance.traceId)
+ // TODO: this logic will take ~200ms each trigger, need to root cause and re-enable once it's fixed, or it should only be invoked when the tutorial is actually needed
+ // await telemetry.withTraceId(async () => {
+ // if (!this._isReady) {
+ // return
+ // }
+ // if (this._currentState instanceof ManualtriggerState) {
+ // if (
+ // triggerType === vscode.InlineCompletionTriggerKind.Invoke &&
+ // this._currentState.hasManualTrigger === false
+ // ) {
+ // this._currentState.hasManualTrigger = true
+ // }
+ // if (
+ // this.sessionManager.getActiveRecommendation().length > 0 &&
+ // this._currentState.hasValidResponse === false
+ // ) {
+ // this._currentState.hasValidResponse = true
+ // }
+ // }
+ // await this.refresh(vscode.window.activeTextEditor, 'codewhisperer')
+ // }, TelemetryHelper.instance.traceId)
}
isTutorialDone(): boolean {
From 69516f4dff84440496110fbfd7ca7f06956b557b Mon Sep 17 00:00:00 2001
From: Lei Gao <97199248+leigaol@users.noreply.github.com>
Date: Thu, 24 Jul 2025 16:02:43 -0700
Subject: [PATCH 02/18] fix(amazonq): early stop pagination requests when user
decision is made (#7759)
## Problem
When a paginated response is in flight, but the user already accepted or
rejected a completion, the subsequent paginated requests should be
cancelled.
This PR also fixes the `codewhispererTotalShownTime` being negative
issue by using performance.now() across all timestamp computation.
## Solution
This is not a user facing change.
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/src/app/inline/completion.ts | 7 +++++++
.../amazonq/src/app/inline/recommendationService.ts | 13 +++++++++----
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/packages/amazonq/src/app/inline/completion.ts b/packages/amazonq/src/app/inline/completion.ts
index 9aaf40c4c43..491fe9fb0d2 100644
--- a/packages/amazonq/src/app/inline/completion.ts
+++ b/packages/amazonq/src/app/inline/completion.ts
@@ -164,6 +164,11 @@ export class InlineCompletionManager implements Disposable {
const onInlineRejection = async () => {
try {
vsCodeState.isCodeWhispererEditing = true
+ if (this.sessionManager.getActiveSession() === undefined) {
+ return
+ }
+ const requestStartTime = this.sessionManager.getActiveSession()!.requestStartTime
+ const totalSessionDisplayTime = performance.now() - requestStartTime
await commands.executeCommand('editor.action.inlineSuggest.hide')
// TODO: also log the seen state for other suggestions in session
this.disposable.dispose()
@@ -185,6 +190,7 @@ export class InlineCompletionManager implements Disposable {
discarded: false,
},
},
+ totalSessionDisplayTime: totalSessionDisplayTime,
}
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
// clear session manager states once rejected
@@ -314,6 +320,7 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
discarded: false,
},
},
+ totalSessionDisplayTime: performance.now() - prevSession.requestStartTime,
}
this.languageClient.sendNotification(this.logSessionResultMessageName, params)
this.sessionManager.clear()
diff --git a/packages/amazonq/src/app/inline/recommendationService.ts b/packages/amazonq/src/app/inline/recommendationService.ts
index c7ba5c3b4b7..794d6c46183 100644
--- a/packages/amazonq/src/app/inline/recommendationService.ts
+++ b/packages/amazonq/src/app/inline/recommendationService.ts
@@ -12,10 +12,10 @@ import {
import { CancellationToken, InlineCompletionContext, Position, TextDocument } from 'vscode'
import { LanguageClient } from 'vscode-languageclient'
import { SessionManager } from './sessionManager'
-import { AuthUtil, CodeWhispererStatusBarManager } from 'aws-core-vscode/codewhisperer'
+import { AuthUtil, CodeWhispererStatusBarManager, vsCodeState } from 'aws-core-vscode/codewhisperer'
import { TelemetryHelper } from './telemetryHelper'
import { ICursorUpdateRecorder } from './cursorUpdateManager'
-import { globals, getLogger } from 'aws-core-vscode/shared'
+import { getLogger } from 'aws-core-vscode/shared'
export interface GetAllRecommendationsOptions {
emitTelemetry?: boolean
@@ -68,7 +68,7 @@ export class RecommendationService {
if (options.editsStreakToken) {
request = { ...request, partialResultToken: options.editsStreakToken }
}
- const requestStartTime = globals.clock.Date.now()
+ const requestStartTime = performance.now()
const statusBar = CodeWhispererStatusBarManager.instance
// Only track telemetry if enabled
@@ -119,7 +119,7 @@ export class RecommendationService {
}
TelemetryHelper.instance.setFirstSuggestionShowTime()
- const firstCompletionDisplayLatency = globals.clock.Date.now() - requestStartTime
+ const firstCompletionDisplayLatency = performance.now() - requestStartTime
this.sessionManager.startSession(
result.sessionId,
result.items,
@@ -186,6 +186,11 @@ export class RecommendationService {
request,
token
)
+ // when pagination is in progress, but user has already accepted or rejected an inline completion
+ // then stop pagination
+ if (this.sessionManager.getActiveSession() === undefined || vsCodeState.isCodeWhispererEditing) {
+ break
+ }
this.sessionManager.updateSessionSuggestions(result.items)
nextToken = result.partialResultToken
}
From cf5d9b30d40603bd7b3ae5ce86a3e3c6c9807098 Mon Sep 17 00:00:00 2001
From: Aidan Ton
Date: Thu, 24 Jul 2025 16:42:37 -0700
Subject: [PATCH 03/18] fix(amazonq): fix line break format when getting the
current text document
---
packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts | 2 +-
packages/core/src/shared/utilities/diffUtils.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts b/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
index 178045afaee..45a615e318e 100644
--- a/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
+++ b/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
@@ -30,7 +30,7 @@ export class SvgGenerationService {
origionalCodeHighlightRange: Range[]
}> {
const textDoc = await vscode.workspace.openTextDocument(filePath)
- const originalCode = textDoc.getText()
+ const originalCode = textDoc.getText().replaceAll('\r\n', '\n')
if (originalCode === '') {
logger.error(`udiff format error`)
throw new ToolkitError('udiff format error')
diff --git a/packages/core/src/shared/utilities/diffUtils.ts b/packages/core/src/shared/utilities/diffUtils.ts
index 64d09c19036..439c87dd7e6 100644
--- a/packages/core/src/shared/utilities/diffUtils.ts
+++ b/packages/core/src/shared/utilities/diffUtils.ts
@@ -25,7 +25,7 @@ import jaroWinkler from 'jaro-winkler'
*/
export async function getPatchedCode(filePath: string, patch: string, snippetMode = false) {
const document = await vscode.workspace.openTextDocument(filePath)
- const fileContent = document.getText()
+ const fileContent = document.getText().replaceAll('\r\n', '\n')
// Usage with the existing getPatchedCode function:
let updatedPatch = patch
From 0769acb2ecd3636db62a43761f1393f1ec6a0dc5 Mon Sep 17 00:00:00 2001
From: Lei Gao <97199248+leigaol@users.noreply.github.com>
Date: Thu, 24 Jul 2025 23:34:25 -0700
Subject: [PATCH 04/18] fix(amazonq): Faster and more responsive auto trigger
UX. (#7763)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Problem
1. when intelliSense suggestion is surfaced, inline completion are not
shown even if provideInlineCompletionItems have returned valid items.
2. provideInlineCompletionItems is unnecessarily debounced at 200ms
which creates a user perceivable delay for inline completion UX. We
already blocked concurrent API call.
3. The items returned by provideInlineCompletionItems, even though they
match the typeahead of the user's current editor (they can be
presented), sometimes VS Code decides to not show them to avoid
interrupting user's typing flow. This not show suggestion decision is
not emitted as API callback or events, causing us to report the
suggestion as Rejected but it should be Discard.
## Solution
1. Force the item to render by calling
`editor.action.inlineSuggest.trigger` command.
3. Remove the debounce
5. Use a specific command with when clause context to detect if a
suggestion is shown or not.
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
...-7261a487-e80a-440f-b311-2688e256a886.json | 4 ++
packages/amazonq/package.json | 4 ++
packages/amazonq/src/app/inline/completion.ts | 37 ++++++++++++-------
.../amazonq/src/app/inline/sessionManager.ts | 9 +++++
packages/amazonq/src/lsp/client.ts | 4 ++
.../amazonq/apps/inline/completion.test.ts | 4 +-
6 files changed, 47 insertions(+), 15 deletions(-)
create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
diff --git a/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json b/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
new file mode 100644
index 00000000000..29d129cc287
--- /dev/null
+++ b/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
@@ -0,0 +1,4 @@
+{
+ "type": "Bug Fix",
+ "description": "Faster and more responsive inline completion UX"
+}
diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json
index 39f4f270406..d791ba05af3 100644
--- a/packages/amazonq/package.json
+++ b/packages/amazonq/package.json
@@ -975,6 +975,10 @@
"command": "aws.amazonq.showPrev",
"when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
},
+ {
+ "command": "aws.amazonq.checkInlineSuggestionVisibility",
+ "when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected"
+ },
{
"command": "aws.amazonq.inline.invokeChat",
"win": "ctrl+i",
diff --git a/packages/amazonq/src/app/inline/completion.ts b/packages/amazonq/src/app/inline/completion.ts
index 491fe9fb0d2..66668be1849 100644
--- a/packages/amazonq/src/app/inline/completion.ts
+++ b/packages/amazonq/src/app/inline/completion.ts
@@ -2,7 +2,7 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
-
+import * as vscode from 'vscode'
import {
CancellationToken,
InlineCompletionContext,
@@ -32,7 +32,6 @@ import {
ImportAdderProvider,
CodeSuggestionsState,
vsCodeState,
- inlineCompletionsDebounceDelay,
noInlineSuggestionsMsg,
getDiagnosticsDifferences,
getDiagnosticsOfCurrentFile,
@@ -42,7 +41,7 @@ import { LineTracker } from './stateTracker/lineTracker'
import { InlineTutorialAnnotation } from './tutorials/inlineTutorialAnnotation'
import { TelemetryHelper } from './telemetryHelper'
import { Experiments, getLogger, sleep } from 'aws-core-vscode/shared'
-import { debounce, messageUtils } from 'aws-core-vscode/utils'
+import { messageUtils } from 'aws-core-vscode/utils'
import { showEdits } from './EditRendering/imageRenderer'
import { ICursorUpdateRecorder } from './cursorUpdateManager'
import { DocumentEventListener } from './documentEventListener'
@@ -214,13 +213,23 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
) {}
private readonly logSessionResultMessageName = 'aws/logInlineCompletionSessionResults'
- provideInlineCompletionItems = debounce(
- this._provideInlineCompletionItems.bind(this),
- inlineCompletionsDebounceDelay,
- true
- )
- private async _provideInlineCompletionItems(
+ // Ideally use this API handleDidShowCompletionItem
+ // https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts#L83
+ // we need this because the returned items of provideInlineCompletionItems may not be actually rendered on screen
+ // if VS Code believes the user is actively typing then it will not show such item
+ async checkWhetherInlineCompletionWasShown() {
+ // this line is to force VS Code to re-render the inline completion
+ // if it decides the inline completion can be shown
+ await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
+ // yield event loop to let backend state transition finish plus wait for vsc to render
+ await sleep(10)
+ // run the command to detect if inline suggestion is really shown or not
+ await vscode.commands.executeCommand(`aws.amazonq.checkInlineSuggestionVisibility`)
+ }
+
+ // this method is automatically invoked by VS Code as user types
+ async provideInlineCompletionItems(
document: TextDocument,
position: Position,
context: InlineCompletionContext,
@@ -307,17 +316,18 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
if (prevItemMatchingPrefix.length > 0) {
logstr += `- not call LSP and reuse previous suggestions that match user typed characters
- duration between trigger to completion suggestion is displayed ${performance.now() - t0}`
+ void this.checkWhetherInlineCompletionWasShown()
return prevItemMatchingPrefix
}
- getLogger().debug(`Auto rejecting suggestions from previous session`)
- // if no such suggestions, report the previous suggestion as Reject
+
+ // if no such suggestions, report the previous suggestion as Reject or Discarded
const params: LogInlineCompletionSessionResultsParams = {
sessionId: prevSessionId,
completionSessionResult: {
[prevItemId]: {
- seen: true,
+ seen: prevSession.displayed,
accepted: false,
- discarded: false,
+ discarded: !prevSession.displayed,
},
},
totalSessionDisplayTime: performance.now() - prevSession.requestStartTime,
@@ -461,6 +471,7 @@ ${itemLog}
this.sessionManager.updateCodeReferenceAndImports()
// suggestions returned here will be displayed on screen
logstr += `- duration between trigger to completion suggestion is displayed: ${performance.now() - t0}ms`
+ void this.checkWhetherInlineCompletionWasShown()
return itemsMatchingTypeahead as InlineCompletionItem[]
} catch (e) {
getLogger('amazonqLsp').error('Failed to provide completion items: %O', e)
diff --git a/packages/amazonq/src/app/inline/sessionManager.ts b/packages/amazonq/src/app/inline/sessionManager.ts
index eaa6eaa23b9..7decf035b9a 100644
--- a/packages/amazonq/src/app/inline/sessionManager.ts
+++ b/packages/amazonq/src/app/inline/sessionManager.ts
@@ -24,6 +24,8 @@ export interface CodeWhispererSession {
// partialResultToken for the next trigger if user accepts an EDITS suggestion
editsStreakPartialResultToken?: number | string
triggerOnAcceptance?: boolean
+ // whether any suggestion in this session was displayed on screen
+ displayed: boolean
}
export class SessionManager {
@@ -49,6 +51,7 @@ export class SessionManager {
startPosition,
firstCompletionDisplayLatency,
diagnosticsBeforeAccept,
+ displayed: false,
}
this._currentSuggestionIndex = 0
}
@@ -128,6 +131,12 @@ export class SessionManager {
}
}
+ public checkInlineSuggestionVisibility() {
+ if (this.activeSession) {
+ this.activeSession.displayed = true
+ }
+ }
+
private clearReferenceInlineHintsAndImportHints() {
ReferenceInlineProvider.instance.removeInlineReference()
ImportAdderProvider.instance.clear()
diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts
index 4d052912c8e..e56a4a784b6 100644
--- a/packages/amazonq/src/lsp/client.ts
+++ b/packages/amazonq/src/lsp/client.ts
@@ -352,6 +352,10 @@ async function onLanguageServerReady(
await vscode.commands.executeCommand('editor.action.inlineSuggest.showNext')
sessionManager.onNextSuggestion()
}),
+ // this is a workaround since handleDidShowCompletionItem is not public API
+ Commands.register('aws.amazonq.checkInlineSuggestionVisibility', async () => {
+ sessionManager.checkInlineSuggestionVisibility()
+ }),
Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => {
await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger')
}),
diff --git a/packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts b/packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts
index 7b079eaad17..417c8be1426 100644
--- a/packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts
+++ b/packages/amazonq/test/unit/amazonq/apps/inline/completion.test.ts
@@ -378,7 +378,7 @@ describe('InlineCompletionManager', () => {
)
await messageShown
})
- describe('debounce behavior', function () {
+ describe.skip('debounce behavior', function () {
let clock: ReturnType
beforeEach(function () {
@@ -389,7 +389,7 @@ describe('InlineCompletionManager', () => {
clock.uninstall()
})
- it('should only trigger once on rapid events', async () => {
+ it.skip('should only trigger once on rapid events', async () => {
provider = new AmazonQInlineCompletionItemProvider(
languageClient,
recommendationService,
From 64fae7259c354d469a8597db1658ad9beb54e4aa Mon Sep 17 00:00:00 2001
From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com>
Date: Fri, 25 Jul 2025 09:41:20 -0700
Subject: [PATCH 05/18] refactor(amazonq): Removing unwanted /dev and /doc code
(#7760)
## Problem
- There is lot of duplicate and unwanted redundant code in
[aws-toolkit-vscode](https://github.com/aws/aws-toolkit-vscode)
repository.
## Solution
- This is the 2nd PR to remove unwanted code.
- Here is the 1st PR:
https://github.com/aws/aws-toolkit-vscode/pull/7735
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
.gitignore | 1 -
.../session/chatSessionStorage.test.ts | 25 -
.../amazonqFeatureDev/session/session.test.ts | 129 -
.../unit/amazonqFeatureDev/util/files.test.ts | 172 -
.../scripts/build/generateServiceClient.ts | 4 -
.../commons/connector/baseMessenger.ts | 219 -
.../commons/connector/connectorMessages.ts | 291 -
.../commons/session/sessionConfigFactory.ts | 38 -
packages/core/src/amazonq/commons/types.ts | 39 -
packages/core/src/amazonq/index.ts | 1 -
packages/core/src/amazonq/indexNode.ts | 2 -
.../core/src/amazonq/session/sessionState.ts | 432 --
packages/core/src/amazonq/util/files.ts | 301 -
packages/core/src/amazonq/util/upload.ts | 5 +-
packages/core/src/amazonqDoc/app.ts | 103 -
packages/core/src/amazonqDoc/constants.ts | 163 -
.../amazonqDoc/controllers/chat/controller.ts | 715 ---
.../controllers/docGenerationTask.ts | 100 -
packages/core/src/amazonqDoc/errors.ts | 63 -
packages/core/src/amazonqDoc/index.ts | 10 -
packages/core/src/amazonqDoc/messenger.ts | 65 -
.../core/src/amazonqDoc/session/session.ts | 371 --
.../src/amazonqDoc/session/sessionState.ts | 165 -
.../src/amazonqDoc/storages/chatSession.ts | 23 -
packages/core/src/amazonqDoc/types.ts | 83 -
.../views/actions/uiMessageListener.ts | 168 -
packages/core/src/amazonqFeatureDev/app.ts | 106 -
.../codewhispererruntime-2022-11-11.json | 5640 -----------------
.../amazonqFeatureDev/client/featureDev.ts | 376 --
.../core/src/amazonqFeatureDev/constants.ts | 33 -
.../controllers/chat/controller.ts | 1089 ----
.../controllers/chat/messenger/constants.ts | 6 -
packages/core/src/amazonqFeatureDev/errors.ts | 191 -
packages/core/src/amazonqFeatureDev/index.ts | 15 -
packages/core/src/amazonqFeatureDev/limits.ts | 14 -
packages/core/src/amazonqFeatureDev/models.ts | 12 -
.../src/amazonqFeatureDev/session/session.ts | 412 --
.../amazonqFeatureDev/session/sessionState.ts | 285 -
.../amazonqFeatureDev/storages/chatSession.ts | 23 -
.../src/amazonqFeatureDev/userFacingText.ts | 25 -
.../views/actions/uiMessageListener.ts | 169 -
.../transformByQ/humanInTheLoopManager.ts | 9 +-
.../transformByQ/transformFileHandler.ts | 2 +-
packages/core/src/shared/constants.ts | 9 +
packages/core/src/shared/errors.ts | 17 +-
.../src/shared/utilities/workspaceUtils.ts | 3 +-
.../core/src/test/amazonq/common/diff.test.ts | 2 +-
.../test/amazonq/session/sessionState.test.ts | 153 -
.../src/test/amazonq/session/testSetup.ts | 74 -
packages/core/src/test/amazonq/utils.ts | 182 -
.../src/test/amazonqDoc/controller.test.ts | 577 --
.../core/src/test/amazonqDoc/mockContent.ts | 86 -
.../amazonqDoc/session/sessionState.test.ts | 29 -
packages/core/src/test/amazonqDoc/utils.ts | 269 -
.../controllers/chat/controller.test.ts | 717 ---
.../session/sessionState.test.ts | 95 -
packages/core/src/test/index.ts | 1 -
.../testInteg/perf/prepareRepoData.test.ts | 84 -
.../testInteg/perf/registerNewFiles.test.ts | 89 -
59 files changed, 39 insertions(+), 14443 deletions(-)
delete mode 100644 packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts
delete mode 100644 packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts
delete mode 100644 packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts
delete mode 100644 packages/core/src/amazonq/commons/connector/baseMessenger.ts
delete mode 100644 packages/core/src/amazonq/commons/connector/connectorMessages.ts
delete mode 100644 packages/core/src/amazonq/commons/session/sessionConfigFactory.ts
delete mode 100644 packages/core/src/amazonq/session/sessionState.ts
delete mode 100644 packages/core/src/amazonq/util/files.ts
delete mode 100644 packages/core/src/amazonqDoc/app.ts
delete mode 100644 packages/core/src/amazonqDoc/constants.ts
delete mode 100644 packages/core/src/amazonqDoc/controllers/chat/controller.ts
delete mode 100644 packages/core/src/amazonqDoc/controllers/docGenerationTask.ts
delete mode 100644 packages/core/src/amazonqDoc/errors.ts
delete mode 100644 packages/core/src/amazonqDoc/index.ts
delete mode 100644 packages/core/src/amazonqDoc/messenger.ts
delete mode 100644 packages/core/src/amazonqDoc/session/session.ts
delete mode 100644 packages/core/src/amazonqDoc/session/sessionState.ts
delete mode 100644 packages/core/src/amazonqDoc/storages/chatSession.ts
delete mode 100644 packages/core/src/amazonqDoc/types.ts
delete mode 100644 packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/app.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json
delete mode 100644 packages/core/src/amazonqFeatureDev/client/featureDev.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/constants.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/controllers/chat/messenger/constants.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/errors.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/index.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/limits.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/models.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/session/session.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/session/sessionState.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/storages/chatSession.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/userFacingText.ts
delete mode 100644 packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts
delete mode 100644 packages/core/src/test/amazonq/session/sessionState.test.ts
delete mode 100644 packages/core/src/test/amazonq/session/testSetup.ts
delete mode 100644 packages/core/src/test/amazonq/utils.ts
delete mode 100644 packages/core/src/test/amazonqDoc/controller.test.ts
delete mode 100644 packages/core/src/test/amazonqDoc/mockContent.ts
delete mode 100644 packages/core/src/test/amazonqDoc/session/sessionState.test.ts
delete mode 100644 packages/core/src/test/amazonqDoc/utils.ts
delete mode 100644 packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts
delete mode 100644 packages/core/src/test/amazonqFeatureDev/session/sessionState.test.ts
delete mode 100644 packages/core/src/testInteg/perf/prepareRepoData.test.ts
delete mode 100644 packages/core/src/testInteg/perf/registerNewFiles.test.ts
diff --git a/.gitignore b/.gitignore
index 596af538b2e..3541dbf9cae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,7 +31,6 @@ src.gen/*
**/src/shared/telemetry/clienttelemetry.d.ts
**/src/codewhisperer/client/codewhispererclient.d.ts
**/src/codewhisperer/client/codewhispereruserclient.d.ts
-**/src/amazonqFeatureDev/client/featuredevproxyclient.d.ts
**/src/auth/sso/oidcclientpkce.d.ts
# Generated by tests
diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts
deleted file mode 100644
index 4c6073114f8..00000000000
--- a/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as assert from 'assert'
-import { FeatureDevChatSessionStorage } from 'aws-core-vscode/amazonqFeatureDev'
-import { Messenger } from 'aws-core-vscode/amazonq'
-import { createMessenger } from 'aws-core-vscode/test'
-
-describe('chatSession', () => {
- const tabID = '1234'
- let chatStorage: FeatureDevChatSessionStorage
- let messenger: Messenger
-
- beforeEach(() => {
- messenger = createMessenger()
- chatStorage = new FeatureDevChatSessionStorage(messenger)
- })
-
- it('locks getSession', async () => {
- const results = await Promise.allSettled([chatStorage.getSession(tabID), chatStorage.getSession(tabID)])
- assert.equal(results.length, 2)
- assert.deepStrictEqual(results[0], results[1])
- })
-})
diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts
deleted file mode 100644
index 39c38de555f..00000000000
--- a/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as assert from 'assert'
-
-import sinon from 'sinon'
-
-import {
- ControllerSetup,
- createController,
- createMessenger,
- createSession,
- generateVirtualMemoryUri,
- sessionRegisterProvider,
- sessionWriteFile,
- assertTelemetry,
-} from 'aws-core-vscode/test'
-import { FeatureDevClient, featureDevScheme, FeatureDevCodeGenState } from 'aws-core-vscode/amazonqFeatureDev'
-import { Messenger, CurrentWsFolders } from 'aws-core-vscode/amazonq'
-import path from 'path'
-import { fs } from 'aws-core-vscode/shared'
-
-describe('session', () => {
- const conversationID = '12345'
- let messenger: Messenger
-
- beforeEach(() => {
- messenger = createMessenger()
- })
-
- afterEach(() => {
- sinon.restore()
- })
-
- describe('preloader', () => {
- it('emits start chat telemetry', async () => {
- const session = await createSession({ messenger, conversationID, scheme: featureDevScheme })
- session.latestMessage = 'implement twosum in typescript'
-
- await session.preloader()
-
- assertTelemetry('amazonq_startConversationInvoke', {
- amazonqConversationId: conversationID,
- })
- })
- })
- describe('insertChanges', async () => {
- afterEach(() => {
- sinon.restore()
- })
-
- let workspaceFolderUriFsPath: string
- const notRejectedFileName = 'notRejectedFile.js'
- const notRejectedFileContent = 'notrejectedFileContent'
- let uri: vscode.Uri
- let encodedContent: Uint8Array
-
- async function createCodeGenState() {
- const controllerSetup: ControllerSetup = await createController()
-
- const uploadID = '789'
- const tabID = '123'
- const workspaceFolders = [controllerSetup.workspaceFolder] as CurrentWsFolders
- workspaceFolderUriFsPath = controllerSetup.workspaceFolder.uri.fsPath
- uri = generateVirtualMemoryUri(uploadID, notRejectedFileName, featureDevScheme)
-
- const testConfig = {
- conversationId: conversationID,
- proxyClient: {} as unknown as FeatureDevClient,
- workspaceRoots: [''],
- uploadId: uploadID,
- workspaceFolders,
- }
-
- const codeGenState = new FeatureDevCodeGenState(
- testConfig,
- [
- {
- zipFilePath: notRejectedFileName,
- relativePath: notRejectedFileName,
- fileContent: notRejectedFileContent,
- rejected: false,
- virtualMemoryUri: uri,
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- {
- zipFilePath: 'rejectedFile.js',
- relativePath: 'rejectedFile.js',
- fileContent: 'rejectedFileContent',
- rejected: true,
- virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'rejectedFile.js', featureDevScheme),
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- ],
- [],
- [],
- tabID,
- 0,
- {}
- )
- const session = await createSession({
- messenger,
- sessionState: codeGenState,
- conversationID,
- scheme: featureDevScheme,
- })
- encodedContent = new TextEncoder().encode(notRejectedFileContent)
- await sessionRegisterProvider(session, uri, encodedContent)
- return session
- }
- it('only insert non rejected files', async () => {
- const fsSpyWriteFile = sinon.spy(fs, 'writeFile')
- const session = await createCodeGenState()
- sinon.stub(session, 'sendLinesOfCodeAcceptedTelemetry').resolves()
- await sessionWriteFile(session, uri, encodedContent)
- await session.insertChanges()
-
- const absolutePath = path.join(workspaceFolderUriFsPath, notRejectedFileName)
-
- assert.ok(fsSpyWriteFile.calledOnce)
- assert.ok(fsSpyWriteFile.calledWith(absolutePath, notRejectedFileContent))
- })
- })
-})
diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts
deleted file mode 100644
index 574d0a25a19..00000000000
--- a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as vscode from 'vscode'
-import assert from 'assert'
-import {
- prepareRepoData,
- PrepareRepoDataOptions,
- TelemetryHelper,
- maxRepoSizeBytes,
-} from 'aws-core-vscode/amazonqFeatureDev'
-import { assertTelemetry, getWorkspaceFolder, TestFolder } from 'aws-core-vscode/test'
-import { fs, AmazonqCreateUpload, ZipStream, ContentLengthError } from 'aws-core-vscode/shared'
-import { MetricName, Span } from 'aws-core-vscode/telemetry'
-import sinon from 'sinon'
-import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer'
-import { CurrentWsFolders } from 'aws-core-vscode/amazonq'
-import path from 'path'
-
-const testDevfilePrepareRepo = async (devfileEnabled: boolean) => {
- const files: Record = {
- 'file.md': 'test content',
- // only include when execution is enabled
- 'devfile.yaml': 'test',
- // .git folder is always dropped (because of vscode global exclude rules)
- '.git/ref': '####',
- // .gitignore should always be included
- '.gitignore': 'node_models/*',
- // non code files only when dev execution is enabled
- 'abc.jar': 'jar-content',
- 'data/logo.ico': 'binary-content',
- }
- const folder = await TestFolder.create()
-
- for (const [fileName, content] of Object.entries(files)) {
- await folder.write(fileName, content)
- }
-
- const expectedFiles = !devfileEnabled
- ? ['file.md', '.gitignore']
- : ['devfile.yaml', 'file.md', '.gitignore', 'abc.jar', 'data/logo.ico']
-
- const workspace = getWorkspaceFolder(folder.path)
- sinon
- .stub(CodeWhispererSettings.instance, 'getAutoBuildSetting')
- .returns(devfileEnabled ? { [workspace.uri.fsPath]: true } : {})
-
- await testPrepareRepoData([workspace], expectedFiles, { telemetry: new TelemetryHelper() })
-}
-
-const testPrepareRepoData = async (
- workspaces: vscode.WorkspaceFolder[],
- expectedFiles: string[],
- prepareRepoDataOptions: PrepareRepoDataOptions,
- expectedTelemetryMetrics?: Array<{ metricName: MetricName; value: any }>
-) => {
- expectedFiles.sort((a, b) => a.localeCompare(b))
- const result = await prepareRepoData(
- workspaces.map((ws) => ws.uri.fsPath),
- workspaces as CurrentWsFolders,
- {
- record: () => {},
- } as unknown as Span,
- prepareRepoDataOptions
- )
-
- assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true)
- // checksum is not the same across different test executions because some unique random folder names are generated
- assert.strictEqual(result.zipFileChecksum.length, 44)
-
- if (expectedTelemetryMetrics) {
- for (const metric of expectedTelemetryMetrics) {
- assertTelemetry(metric.metricName, metric.value)
- }
- }
-
- // Unzip the buffer and compare the entry names
- const zipEntries = await ZipStream.unzip(result.zipFileBuffer)
- const actualZipEntries = zipEntries.map((entry) => entry.filename)
- actualZipEntries.sort((a, b) => a.localeCompare(b))
- assert.deepStrictEqual(actualZipEntries, expectedFiles)
-}
-
-describe('file utils', () => {
- describe('prepareRepoData', function () {
- const defaultPrepareRepoDataOptions: PrepareRepoDataOptions = { telemetry: new TelemetryHelper() }
-
- afterEach(() => {
- sinon.restore()
- })
-
- it('returns files in the workspace as a zip', async function () {
- const folder = await TestFolder.create()
- await folder.write('file1.md', 'test content')
- await folder.write('file2.md', 'test content')
- await folder.write('docs/infra.svg', 'test content')
- const workspace = getWorkspaceFolder(folder.path)
-
- await testPrepareRepoData([workspace], ['file1.md', 'file2.md'], defaultPrepareRepoDataOptions)
- })
-
- it('infrastructure diagram is included', async function () {
- const folder = await TestFolder.create()
- await folder.write('file1.md', 'test content')
- await folder.write('file2.svg', 'test content')
- await folder.write('docs/infra.svg', 'test content')
- const workspace = getWorkspaceFolder(folder.path)
-
- await testPrepareRepoData([workspace], ['file1.md', 'docs/infra.svg'], {
- telemetry: new TelemetryHelper(),
- isIncludeInfraDiagram: true,
- })
- })
-
- it('prepareRepoData ignores denied file extensions', async function () {
- const folder = await TestFolder.create()
- await folder.write('file.mp4', 'test content')
- const workspace = getWorkspaceFolder(folder.path)
-
- await testPrepareRepoData([workspace], [], defaultPrepareRepoDataOptions, [
- { metricName: 'amazonq_bundleExtensionIgnored', value: { filenameExt: 'mp4', count: 1 } },
- ])
- })
-
- it('should ignore devfile.yaml when setting is disabled', async function () {
- await testDevfilePrepareRepo(false)
- })
-
- it('should include devfile.yaml when setting is enabled', async function () {
- await testDevfilePrepareRepo(true)
- })
-
- // Test the logic that allows the customer to modify root source folder
- it('prepareRepoData throws a ContentLengthError code when repo is too big', async function () {
- const folder = await TestFolder.create()
- await folder.write('file.md', 'test content')
- const workspace = getWorkspaceFolder(folder.path)
-
- sinon.stub(fs, 'stat').resolves({ size: 2 * maxRepoSizeBytes } as vscode.FileStat)
- await assert.rejects(
- () =>
- prepareRepoData(
- [workspace.uri.fsPath],
- [workspace],
- {
- record: () => {},
- } as unknown as Span,
- defaultPrepareRepoDataOptions
- ),
- ContentLengthError
- )
- })
-
- it('prepareRepoData properly handles multi-root workspaces', async function () {
- const folder = await TestFolder.create()
- const testFilePath = 'innerFolder/file.md'
- await folder.write(testFilePath, 'test content')
-
- // Add a folder and its subfolder to the workspace
- const workspace1 = getWorkspaceFolder(folder.path)
- const workspace2 = getWorkspaceFolder(folder.path + '/innerFolder')
- const folderName = path.basename(folder.path)
-
- await testPrepareRepoData(
- [workspace1, workspace2],
- [`${folderName}_${workspace1.name}/${testFilePath}`],
- defaultPrepareRepoDataOptions
- )
- })
- })
-})
diff --git a/packages/core/scripts/build/generateServiceClient.ts b/packages/core/scripts/build/generateServiceClient.ts
index 7ef217be21b..5d1854527b9 100644
--- a/packages/core/scripts/build/generateServiceClient.ts
+++ b/packages/core/scripts/build/generateServiceClient.ts
@@ -241,10 +241,6 @@ void (async () => {
serviceJsonPath: 'src/codewhisperer/client/user-service-2.json',
serviceName: 'CodeWhispererUserClient',
},
- {
- serviceJsonPath: 'src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json',
- serviceName: 'FeatureDevProxyClient',
- },
]
await generateServiceClients(serviceClientDefinitions)
})()
diff --git a/packages/core/src/amazonq/commons/connector/baseMessenger.ts b/packages/core/src/amazonq/commons/connector/baseMessenger.ts
deleted file mode 100644
index c26834c6fff..00000000000
--- a/packages/core/src/amazonq/commons/connector/baseMessenger.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatItemAction, ProgressField } from '@aws/mynah-ui'
-import { AuthFollowUpType, AuthMessageDataMap } from '../../../amazonq/auth/model'
-import { i18n } from '../../../shared/i18n-helper'
-import { CodeReference } from '../../../amazonq/webview/ui/connector'
-
-import { MessengerTypes } from '../../../amazonqFeatureDev/controllers/chat/messenger/constants'
-import {
- AppToWebViewMessageDispatcher,
- AsyncEventProgressMessage,
- AuthenticationUpdateMessage,
- AuthNeededException,
- ChatInputEnabledMessage,
- ChatMessage,
- CodeResultMessage,
- FileComponent,
- FolderConfirmationMessage,
- OpenNewTabMessage,
- UpdateAnswerMessage,
- UpdatePlaceholderMessage,
- UpdatePromptProgressMessage,
-} from './connectorMessages'
-import { DeletedFileInfo, FollowUpTypes, NewFileInfo } from '../types'
-import { messageWithConversationId } from '../../../amazonqFeatureDev/userFacingText'
-import { FeatureAuthState } from '../../../codewhisperer/util/authUtil'
-
-export class Messenger {
- public constructor(
- private readonly dispatcher: AppToWebViewMessageDispatcher,
- private readonly sender: string
- ) {}
-
- public sendAnswer(params: {
- message?: string
- type: MessengerTypes
- followUps?: ChatItemAction[]
- tabID: string
- canBeVoted?: boolean
- snapToTop?: boolean
- messageId?: string
- disableChatInput?: boolean
- }) {
- this.dispatcher.sendChatMessage(
- new ChatMessage(
- {
- message: params.message,
- messageType: params.type,
- followUps: params.followUps,
- relatedSuggestions: undefined,
- canBeVoted: params.canBeVoted ?? false,
- snapToTop: params.snapToTop ?? false,
- messageId: params.messageId,
- },
- params.tabID,
- this.sender
- )
- )
- if (params.disableChatInput) {
- this.sendChatInputEnabled(params.tabID, false)
- }
- }
-
- public sendFeedback(tabID: string) {
- this.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.sendFeedback'),
- type: FollowUpTypes.SendFeedback,
- status: 'info',
- },
- ],
- tabID,
- })
- }
-
- public sendMonthlyLimitError(tabID: string) {
- this.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'),
- disableChatInput: true,
- })
- this.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.placeholder.chatInputDisabled'))
- }
-
- public sendUpdatePromptProgress(tabID: string, progressField: ProgressField | null) {
- this.dispatcher.sendUpdatePromptProgress(new UpdatePromptProgressMessage(tabID, this.sender, progressField))
- }
-
- public sendFolderConfirmationMessage(
- tabID: string,
- message: string,
- folderPath: string,
- followUps?: ChatItemAction[]
- ) {
- this.dispatcher.sendFolderConfirmationMessage(
- new FolderConfirmationMessage(tabID, this.sender, message, folderPath, followUps)
- )
-
- this.sendChatInputEnabled(tabID, false)
- }
-
- public sendErrorMessage(
- errorMessage: string,
- tabID: string,
- retries: number,
- conversationId?: string,
- showDefaultMessage?: boolean
- ) {
- if (retries === 0) {
- this.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: showDefaultMessage ? errorMessage : i18n('AWS.amazonq.featureDev.error.technicalDifficulties'),
- canBeVoted: true,
- })
- this.sendFeedback(tabID)
- return
- }
-
- this.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: errorMessage + messageWithConversationId(conversationId),
- })
-
- this.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.retry'),
- type: FollowUpTypes.Retry,
- status: 'warning',
- },
- ],
- tabID,
- })
- }
-
- public sendCodeResult(
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- references: CodeReference[],
- tabID: string,
- uploadId: string,
- codeGenerationId: string
- ) {
- this.dispatcher.sendCodeResult(
- new CodeResultMessage(filePaths, deletedFiles, references, tabID, this.sender, uploadId, codeGenerationId)
- )
- }
-
- public sendAsyncEventProgress(tabID: string, inProgress: boolean, message: string | undefined) {
- this.dispatcher.sendAsyncEventProgress(new AsyncEventProgressMessage(tabID, this.sender, inProgress, message))
- }
-
- public updateFileComponent(
- tabID: string,
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- messageId: string,
- disableFileActions: boolean
- ) {
- this.dispatcher.updateFileComponent(
- new FileComponent(tabID, this.sender, filePaths, deletedFiles, messageId, disableFileActions)
- )
- }
-
- public updateChatAnswer(message: UpdateAnswerMessage) {
- this.dispatcher.updateChatAnswer(message)
- }
-
- public sendUpdatePlaceholder(tabID: string, newPlaceholder: string) {
- this.dispatcher.sendPlaceholder(new UpdatePlaceholderMessage(tabID, this.sender, newPlaceholder))
- }
-
- public sendChatInputEnabled(tabID: string, enabled: boolean) {
- this.dispatcher.sendChatInputEnabled(new ChatInputEnabledMessage(tabID, this.sender, enabled))
- }
-
- public sendAuthenticationUpdate(enabled: boolean, authenticatingTabIDs: string[]) {
- this.dispatcher.sendAuthenticationUpdate(
- new AuthenticationUpdateMessage(this.sender, enabled, authenticatingTabIDs)
- )
- }
-
- public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string) {
- let authType: AuthFollowUpType = 'full-auth'
- let message = AuthMessageDataMap[authType].message
-
- switch (credentialState.amazonQ) {
- case 'disconnected':
- authType = 'full-auth'
- message = AuthMessageDataMap[authType].message
- break
- case 'unsupported':
- authType = 'use-supported-auth'
- message = AuthMessageDataMap[authType].message
- break
- case 'expired':
- authType = 're-auth'
- message = AuthMessageDataMap[authType].message
- break
- }
-
- this.dispatcher.sendAuthNeededExceptionMessage(new AuthNeededException(message, authType, tabID, this.sender))
- }
-
- public openNewTask() {
- this.dispatcher.sendOpenNewTask(new OpenNewTabMessage(this.sender))
- }
-}
diff --git a/packages/core/src/amazonq/commons/connector/connectorMessages.ts b/packages/core/src/amazonq/commons/connector/connectorMessages.ts
deleted file mode 100644
index 6f60b786fcb..00000000000
--- a/packages/core/src/amazonq/commons/connector/connectorMessages.ts
+++ /dev/null
@@ -1,291 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { AuthFollowUpType } from '../../auth/model'
-import { MessagePublisher } from '../../messages/messagePublisher'
-import { CodeReference } from '../../webview/ui/connector'
-import { ChatItemAction, ProgressField, SourceLink } from '@aws/mynah-ui'
-import { ChatItemType } from '../model'
-import { DeletedFileInfo, NewFileInfo } from '../types'
-import { licenseText } from '../../../amazonqFeatureDev/constants'
-
-class UiMessage {
- readonly time: number = Date.now()
- readonly type: string = ''
-
- public constructor(
- protected tabID: string,
- protected sender: string
- ) {}
-}
-
-export class ErrorMessage extends UiMessage {
- readonly title!: string
- readonly message!: string
- override type = 'errorMessage'
-
- constructor(title: string, message: string, tabID: string, sender: string) {
- super(tabID, sender)
- this.title = title
- this.message = message
- }
-}
-
-export class CodeResultMessage extends UiMessage {
- readonly message!: string
- readonly codeGenerationId!: string
- readonly references!: {
- information: string
- recommendationContentSpan: {
- start: number
- end: number
- }
- }[]
- readonly conversationID!: string
- override type = 'codeResultMessage'
-
- constructor(
- readonly filePaths: NewFileInfo[],
- readonly deletedFiles: DeletedFileInfo[],
- references: CodeReference[],
- tabID: string,
- sender: string,
- conversationID: string,
- codeGenerationId: string
- ) {
- super(tabID, sender)
- this.references = references
- .filter((ref) => ref.licenseName && ref.repository && ref.url)
- .map((ref) => {
- return {
- information: licenseText(ref),
-
- // We're forced to provide these otherwise mynah ui errors somewhere down the line. Though they aren't used
- recommendationContentSpan: {
- start: 0,
- end: 0,
- },
- }
- })
- this.codeGenerationId = codeGenerationId
- this.conversationID = conversationID
- }
-}
-
-export class FolderConfirmationMessage extends UiMessage {
- readonly folderPath: string
- readonly message: string
- readonly followUps?: ChatItemAction[]
- override type = 'folderConfirmationMessage'
- constructor(tabID: string, sender: string, message: string, folderPath: string, followUps?: ChatItemAction[]) {
- super(tabID, sender)
- this.message = message
- this.folderPath = folderPath
- this.followUps = followUps
- }
-}
-
-export class UpdatePromptProgressMessage extends UiMessage {
- readonly progressField: ProgressField | null
- override type = 'updatePromptProgress'
- constructor(tabID: string, sender: string, progressField: ProgressField | null) {
- super(tabID, sender)
- this.progressField = progressField
- }
-}
-
-export class AsyncEventProgressMessage extends UiMessage {
- readonly inProgress: boolean
- readonly message: string | undefined
- override type = 'asyncEventProgressMessage'
-
- constructor(tabID: string, sender: string, inProgress: boolean, message: string | undefined) {
- super(tabID, sender)
- this.inProgress = inProgress
- this.message = message
- }
-}
-
-export class AuthenticationUpdateMessage {
- readonly time: number = Date.now()
- readonly type = 'authenticationUpdateMessage'
-
- constructor(
- readonly sender: string,
- readonly featureEnabled: boolean,
- readonly authenticatingTabIDs: string[]
- ) {}
-}
-
-export class FileComponent extends UiMessage {
- readonly filePaths: NewFileInfo[]
- readonly deletedFiles: DeletedFileInfo[]
- override type = 'updateFileComponent'
- readonly messageId: string
- readonly disableFileActions: boolean
-
- constructor(
- tabID: string,
- sender: string,
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- messageId: string,
- disableFileActions: boolean
- ) {
- super(tabID, sender)
- this.filePaths = filePaths
- this.deletedFiles = deletedFiles
- this.messageId = messageId
- this.disableFileActions = disableFileActions
- }
-}
-
-export class UpdatePlaceholderMessage extends UiMessage {
- readonly newPlaceholder: string
- override type = 'updatePlaceholderMessage'
-
- constructor(tabID: string, sender: string, newPlaceholder: string) {
- super(tabID, sender)
- this.newPlaceholder = newPlaceholder
- }
-}
-
-export class ChatInputEnabledMessage extends UiMessage {
- readonly enabled: boolean
- override type = 'chatInputEnabledMessage'
-
- constructor(tabID: string, sender: string, enabled: boolean) {
- super(tabID, sender)
- this.enabled = enabled
- }
-}
-
-export class OpenNewTabMessage {
- readonly time: number = Date.now()
- readonly type = 'openNewTabMessage'
-
- constructor(protected sender: string) {}
-}
-
-export class AuthNeededException extends UiMessage {
- readonly message: string
- readonly authType: AuthFollowUpType
- override type = 'authNeededException'
-
- constructor(message: string, authType: AuthFollowUpType, tabID: string, sender: string) {
- super(tabID, sender)
- this.message = message
- this.authType = authType
- }
-}
-
-export interface ChatMessageProps {
- readonly message: string | undefined
- readonly messageType: ChatItemType
- readonly followUps: ChatItemAction[] | undefined
- readonly relatedSuggestions: SourceLink[] | undefined
- readonly canBeVoted: boolean
- readonly snapToTop: boolean
- readonly messageId?: string
-}
-
-export class ChatMessage extends UiMessage {
- readonly message: string | undefined
- readonly messageType: ChatItemType
- readonly followUps: ChatItemAction[] | undefined
- readonly relatedSuggestions: SourceLink[] | undefined
- readonly canBeVoted: boolean
- readonly requestID!: string
- readonly snapToTop: boolean
- readonly messageId: string | undefined
- override type = 'chatMessage'
-
- constructor(props: ChatMessageProps, tabID: string, sender: string) {
- super(tabID, sender)
- this.message = props.message
- this.messageType = props.messageType
- this.followUps = props.followUps
- this.relatedSuggestions = props.relatedSuggestions
- this.canBeVoted = props.canBeVoted
- this.snapToTop = props.snapToTop
- this.messageId = props.messageId
- }
-}
-
-export interface UpdateAnswerMessageProps {
- readonly messageId: string
- readonly messageType: ChatItemType
- readonly followUps: ChatItemAction[] | undefined
-}
-
-export class UpdateAnswerMessage extends UiMessage {
- readonly messageId: string
- readonly messageType: ChatItemType
- readonly followUps: ChatItemAction[] | undefined
- override type = 'updateChatAnswer'
-
- constructor(props: UpdateAnswerMessageProps, tabID: string, sender: string) {
- super(tabID, sender)
- this.messageId = props.messageId
- this.messageType = props.messageType
- this.followUps = props.followUps
- }
-}
-
-export class AppToWebViewMessageDispatcher {
- constructor(private readonly appsToWebViewMessagePublisher: MessagePublisher) {}
-
- public sendErrorMessage(message: ErrorMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendChatMessage(message: ChatMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendCodeResult(message: CodeResultMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendUpdatePromptProgress(message: UpdatePromptProgressMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendFolderConfirmationMessage(message: FolderConfirmationMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendAsyncEventProgress(message: AsyncEventProgressMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendPlaceholder(message: UpdatePlaceholderMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendChatInputEnabled(message: ChatInputEnabledMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendAuthNeededExceptionMessage(message: AuthNeededException) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendAuthenticationUpdate(message: AuthenticationUpdateMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public sendOpenNewTask(message: OpenNewTabMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public updateFileComponent(message: FileComponent) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-
- public updateChatAnswer(message: UpdateAnswerMessage) {
- this.appsToWebViewMessagePublisher.publish(message)
- }
-}
diff --git a/packages/core/src/amazonq/commons/session/sessionConfigFactory.ts b/packages/core/src/amazonq/commons/session/sessionConfigFactory.ts
deleted file mode 100644
index 4204d1d56d6..00000000000
--- a/packages/core/src/amazonq/commons/session/sessionConfigFactory.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { WorkspaceFolderNotFoundError } from '../../../amazonqFeatureDev/errors'
-import { CurrentWsFolders } from '../types'
-import { VirtualFileSystem } from '../../../shared/virtualFilesystem'
-import { VirtualMemoryFile } from '../../../shared/virtualMemoryFile'
-
-export interface SessionConfig {
- // The paths on disk to where the source code lives
- workspaceRoots: string[]
- readonly fs: VirtualFileSystem
- readonly workspaceFolders: CurrentWsFolders
-}
-
-/**
- * Factory method for creating session configurations
- * @returns An instantiated SessionConfig, using either the arguments provided or the defaults
- */
-export async function createSessionConfig(scheme: string): Promise {
- const workspaceFolders = vscode.workspace.workspaceFolders
- const firstFolder = workspaceFolders?.[0]
- if (workspaceFolders === undefined || workspaceFolders.length === 0 || firstFolder === undefined) {
- throw new WorkspaceFolderNotFoundError()
- }
-
- const workspaceRoots = workspaceFolders.map((f) => f.uri.fsPath)
-
- const fs = new VirtualFileSystem()
-
- // Register an empty featureDev file that's used when a new file is being added by the LLM
- fs.registerProvider(vscode.Uri.from({ scheme, path: 'empty' }), new VirtualMemoryFile(new Uint8Array()))
-
- return Promise.resolve({ workspaceRoots, fs, workspaceFolders: [firstFolder, ...workspaceFolders.slice(1)] })
-}
diff --git a/packages/core/src/amazonq/commons/types.ts b/packages/core/src/amazonq/commons/types.ts
index c2d2c427596..3a4d014609d 100644
--- a/packages/core/src/amazonq/commons/types.ts
+++ b/packages/core/src/amazonq/commons/types.ts
@@ -4,13 +4,8 @@
*/
import * as vscode from 'vscode'
-import { VirtualFileSystem } from '../../shared/virtualFilesystem'
-import type { CancellationTokenSource } from 'vscode'
-import { CodeReference, UploadHistory } from '../webview/ui/connector'
import { DiffTreeFileInfo } from '../webview/ui/diffTree/types'
-import { Messenger } from './connector/baseMessenger'
import { FeatureClient } from '../client/client'
-import { TelemetryHelper } from '../util/telemetryHelper'
import { MynahUI } from '@aws/mynah-ui'
export enum FollowUpTypes {
@@ -56,12 +51,6 @@ export type Interaction = {
responseType?: LLMResponseType
}
-export interface SessionStateInteraction {
- nextState: SessionState | Omit | undefined
- interaction: Interaction
- currentCodeGenerationId?: string
-}
-
export enum Intent {
DEV = 'DEV',
DOC = 'DOC',
@@ -86,24 +75,6 @@ export type SessionStatePhase = DevPhase.INIT | DevPhase.CODEGEN
export type CurrentWsFolders = [vscode.WorkspaceFolder, ...vscode.WorkspaceFolder[]]
-export interface SessionState {
- readonly filePaths?: NewFileInfo[]
- readonly deletedFiles?: DeletedFileInfo[]
- readonly references?: CodeReference[]
- readonly phase?: SessionStatePhase
- readonly uploadId: string
- readonly currentIteration?: number
- currentCodeGenerationId?: string
- tokenSource?: CancellationTokenSource
- readonly codeGenerationId?: string
- readonly tabID: string
- interact(action: SessionStateAction): Promise
- updateWorkspaceRoot?: (workspaceRoot: string) => void
- codeGenerationRemainingIterationCount?: number
- codeGenerationTotalIterationCount?: number
- uploadHistory?: UploadHistory
-}
-
export interface SessionStateConfig {
workspaceRoots: string[]
workspaceFolders: CurrentWsFolders
@@ -113,16 +84,6 @@ export interface SessionStateConfig {
currentCodeGenerationId?: string
}
-export interface SessionStateAction {
- task: string
- msg: string
- messenger: Messenger
- fs: VirtualFileSystem
- telemetry: TelemetryHelper
- uploadHistory?: UploadHistory
- tokenSource?: CancellationTokenSource
-}
-
export type NewFileZipContents = { zipFilePath: string; fileContent: string }
export type NewFileInfo = DiffTreeFileInfo &
NewFileZipContents & {
diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts
index 3b7737b3547..e06b8ad53d9 100644
--- a/packages/core/src/amazonq/index.ts
+++ b/packages/core/src/amazonq/index.ts
@@ -36,7 +36,6 @@ export { ChatItemType, referenceLogText } from './commons/model'
export { ExtensionMessage } from '../amazonq/webview/ui/commands'
export { CodeReference } from '../codewhispererChat/view/connector/connector'
export { extractAuthFollowUp } from './util/authUtils'
-export { Messenger } from './commons/connector/baseMessenger'
export * as secondaryAuth from '../auth/secondaryAuth'
export * as authConnection from '../auth/connection'
export * as featureConfig from './webview/generators/featureConfig'
diff --git a/packages/core/src/amazonq/indexNode.ts b/packages/core/src/amazonq/indexNode.ts
index 628b5d626cd..ccc01dc2832 100644
--- a/packages/core/src/amazonq/indexNode.ts
+++ b/packages/core/src/amazonq/indexNode.ts
@@ -7,6 +7,4 @@
* These agents have underlying requirements on node dependencies (e.g. jsdom, admzip)
*/
export { init as cwChatAppInit } from '../codewhispererChat/app'
-export { init as featureDevChatAppInit } from '../amazonqFeatureDev/app' // TODO: Remove this
export { init as gumbyChatAppInit } from '../amazonqGumby/app'
-export { init as docChatAppInit } from '../amazonqDoc/app' // TODO: Remove this
diff --git a/packages/core/src/amazonq/session/sessionState.ts b/packages/core/src/amazonq/session/sessionState.ts
deleted file mode 100644
index 1f206c23159..00000000000
--- a/packages/core/src/amazonq/session/sessionState.ts
+++ /dev/null
@@ -1,432 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import globals from '../../shared/extensionGlobals'
-import { getLogger } from '../../shared/logger/logger'
-import { AmazonqCreateUpload, Span, telemetry } from '../../shared/telemetry/telemetry'
-import { VirtualFileSystem } from '../../shared/virtualFilesystem'
-import { CodeReference, UploadHistory } from '../webview/ui/connector'
-import { AuthUtil } from '../../codewhisperer/util/authUtil'
-import { randomUUID } from '../../shared/crypto'
-import { i18n } from '../../shared/i18n-helper'
-import {
- CodeGenerationStatus,
- CurrentWsFolders,
- DeletedFileInfo,
- DevPhase,
- NewFileInfo,
- SessionState,
- SessionStateAction,
- SessionStateConfig,
- SessionStateInteraction,
- SessionStatePhase,
-} from '../commons/types'
-import { prepareRepoData, getDeletedFileInfos, registerNewFiles, PrepareRepoDataOptions } from '../util/files'
-import { uploadCode } from '../util/upload'
-import { truncate } from '../../shared/utilities/textUtilities'
-
-export const EmptyCodeGenID = 'EMPTY_CURRENT_CODE_GENERATION_ID'
-export const RunCommandLogFileName = '.amazonq/dev/run_command.log'
-
-export interface BaseMessenger {
- sendAnswer(params: any): void
- sendUpdatePlaceholder?(tabId: string, message: string): void
-}
-
-export abstract class CodeGenBase {
- private pollCount = 360
- private requestDelay = 5000
- public tokenSource: vscode.CancellationTokenSource
- public phase: SessionStatePhase = DevPhase.CODEGEN
- public readonly conversationId: string
- public readonly uploadId: string
- public currentCodeGenerationId?: string
- public isCancellationRequested?: boolean
-
- constructor(
- protected config: SessionStateConfig,
- public tabID: string
- ) {
- this.tokenSource = new vscode.CancellationTokenSource()
- this.conversationId = config.conversationId
- this.uploadId = config.uploadId
- this.currentCodeGenerationId = config.currentCodeGenerationId || EmptyCodeGenID
- }
-
- protected abstract handleProgress(messenger: BaseMessenger, action: SessionStateAction, detail?: string): void
- protected abstract getScheme(): string
- protected abstract getTimeoutErrorCode(): string
- protected abstract handleGenerationComplete(
- messenger: BaseMessenger,
- newFileInfo: NewFileInfo[],
- action: SessionStateAction
- ): void
-
- async generateCode({
- messenger,
- fs,
- codeGenerationId,
- telemetry: telemetry,
- workspaceFolders,
- action,
- }: {
- messenger: BaseMessenger
- fs: VirtualFileSystem
- codeGenerationId: string
- telemetry: any
- workspaceFolders: CurrentWsFolders
- action: SessionStateAction
- }): Promise<{
- newFiles: NewFileInfo[]
- deletedFiles: DeletedFileInfo[]
- references: CodeReference[]
- codeGenerationRemainingIterationCount?: number
- codeGenerationTotalIterationCount?: number
- }> {
- let codeGenerationRemainingIterationCount = undefined
- let codeGenerationTotalIterationCount = undefined
- for (
- let pollingIteration = 0;
- pollingIteration < this.pollCount && !this.isCancellationRequested;
- ++pollingIteration
- ) {
- const codegenResult = await this.config.proxyClient.getCodeGeneration(this.conversationId, codeGenerationId)
- codeGenerationRemainingIterationCount = codegenResult.codeGenerationRemainingIterationCount
- codeGenerationTotalIterationCount = codegenResult.codeGenerationTotalIterationCount
-
- getLogger().debug(`Codegen response: %O`, codegenResult)
- telemetry.setCodeGenerationResult(codegenResult.codeGenerationStatus.status)
-
- switch (codegenResult.codeGenerationStatus.status as CodeGenerationStatus) {
- case CodeGenerationStatus.COMPLETE: {
- const { newFileContents, deletedFiles, references } =
- await this.config.proxyClient.exportResultArchive(this.conversationId)
-
- const logFileInfo = newFileContents.find(
- (file: { zipFilePath: string; fileContent: string }) =>
- file.zipFilePath === RunCommandLogFileName
- )
- if (logFileInfo) {
- logFileInfo.fileContent = truncate(logFileInfo.fileContent, 10000000, '\n... [truncated]') // Limit to max 20MB
- getLogger().info(`sessionState: Run Command logs, ${logFileInfo.fileContent}`)
- newFileContents.splice(newFileContents.indexOf(logFileInfo), 1)
- }
-
- const newFileInfo = registerNewFiles(
- fs,
- newFileContents,
- this.uploadId,
- workspaceFolders,
- this.conversationId,
- this.getScheme()
- )
- telemetry.setNumberOfFilesGenerated(newFileInfo.length)
-
- this.handleGenerationComplete(messenger, newFileInfo, action)
-
- return {
- newFiles: newFileInfo,
- deletedFiles: getDeletedFileInfos(deletedFiles, workspaceFolders),
- references,
- codeGenerationRemainingIterationCount,
- codeGenerationTotalIterationCount,
- }
- }
- case CodeGenerationStatus.PREDICT_READY:
- case CodeGenerationStatus.IN_PROGRESS: {
- if (codegenResult.codeGenerationStatusDetail) {
- this.handleProgress(messenger, action, codegenResult.codeGenerationStatusDetail)
- }
- await new Promise((f) => globals.clock.setTimeout(f, this.requestDelay))
- break
- }
- case CodeGenerationStatus.PREDICT_FAILED:
- case CodeGenerationStatus.DEBATE_FAILED:
- case CodeGenerationStatus.FAILED: {
- throw this.handleError(messenger, codegenResult)
- }
- default: {
- const errorMessage = `Unknown status: ${codegenResult.codeGenerationStatus.status}\n`
- throw new ToolkitError(errorMessage, { code: 'UnknownCodeGenError' })
- }
- }
- }
-
- if (!this.isCancellationRequested) {
- const errorMessage = i18n('AWS.amazonq.featureDev.error.codeGen.timeout')
- throw new ToolkitError(errorMessage, { code: this.getTimeoutErrorCode() })
- }
-
- return {
- newFiles: [],
- deletedFiles: [],
- references: [],
- codeGenerationRemainingIterationCount: codeGenerationRemainingIterationCount,
- codeGenerationTotalIterationCount: codeGenerationTotalIterationCount,
- }
- }
-
- protected abstract handleError(messenger: BaseMessenger, codegenResult: any): Error
-}
-
-export abstract class BasePrepareCodeGenState implements SessionState {
- public tokenSource: vscode.CancellationTokenSource
- public readonly phase = DevPhase.CODEGEN
- public uploadId: string
- public conversationId: string
-
- constructor(
- protected config: SessionStateConfig,
- public filePaths: NewFileInfo[],
- public deletedFiles: DeletedFileInfo[],
- public references: CodeReference[],
- public tabID: string,
- public currentIteration: number,
- public codeGenerationRemainingIterationCount?: number,
- public codeGenerationTotalIterationCount?: number,
- public uploadHistory: UploadHistory = {},
- public superTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(),
- public currentCodeGenerationId?: string,
- public codeGenerationId?: string
- ) {
- this.tokenSource = superTokenSource || new vscode.CancellationTokenSource()
- this.uploadId = config.uploadId
- this.currentCodeGenerationId = currentCodeGenerationId
- this.conversationId = config.conversationId
- this.uploadHistory = uploadHistory
- this.codeGenerationId = codeGenerationId
- }
-
- updateWorkspaceRoot(workspaceRoot: string) {
- this.config.workspaceRoots = [workspaceRoot]
- }
-
- protected createNextState(
- config: SessionStateConfig,
- StateClass?: new (
- config: SessionStateConfig,
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- references: CodeReference[],
- tabID: string,
- currentIteration: number,
- uploadHistory: UploadHistory,
- codeGenerationRemainingIterationCount?: number,
- codeGenerationTotalIterationCount?: number
- ) => SessionState
- ): SessionState {
- return new StateClass!(
- config,
- this.filePaths,
- this.deletedFiles,
- this.references,
- this.tabID,
- this.currentIteration,
- this.uploadHistory
- )
- }
-
- protected abstract preUpload(action: SessionStateAction): void
- protected abstract postUpload(action: SessionStateAction): void
-
- async interact(action: SessionStateAction): Promise {
- this.preUpload(action)
- const uploadId = await telemetry.amazonq_createUpload.run(async (span) => {
- span.record({
- amazonqConversationId: this.config.conversationId,
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
- const { zipFileBuffer, zipFileChecksum } = await this.prepareProjectZip(
- this.config.workspaceRoots,
- this.config.workspaceFolders,
- span,
- { telemetry: action.telemetry }
- )
- const uploadId = randomUUID()
- const { uploadUrl, kmsKeyArn } = await this.config.proxyClient.createUploadUrl(
- this.config.conversationId,
- zipFileChecksum,
- zipFileBuffer.length,
- uploadId
- )
-
- await uploadCode(uploadUrl, zipFileBuffer, zipFileChecksum, kmsKeyArn)
- this.postUpload(action)
-
- return uploadId
- })
-
- this.uploadId = uploadId
- const nextState = this.createNextState({ ...this.config, uploadId })
- return nextState.interact(action)
- }
-
- protected async prepareProjectZip(
- workspaceRoots: string[],
- workspaceFolders: CurrentWsFolders,
- span: Span,
- options: PrepareRepoDataOptions
- ) {
- return await prepareRepoData(workspaceRoots, workspaceFolders, span, options)
- }
-}
-
-export interface CodeGenerationParams {
- messenger: BaseMessenger
- fs: VirtualFileSystem
- codeGenerationId: string
- telemetry: any
- workspaceFolders: CurrentWsFolders
-}
-
-export interface CreateNextStateParams {
- filePaths: NewFileInfo[]
- deletedFiles: DeletedFileInfo[]
- references: CodeReference[]
- currentIteration: number
- remainingIterations?: number
- totalIterations?: number
- uploadHistory: UploadHistory
- tokenSource: vscode.CancellationTokenSource
- currentCodeGenerationId?: string
- codeGenerationId?: string
-}
-
-export abstract class BaseCodeGenState extends CodeGenBase implements SessionState {
- constructor(
- config: SessionStateConfig,
- public filePaths: NewFileInfo[],
- public deletedFiles: DeletedFileInfo[],
- public references: CodeReference[],
- tabID: string,
- public currentIteration: number,
- public uploadHistory: UploadHistory,
- public codeGenerationRemainingIterationCount?: number,
- public codeGenerationTotalIterationCount?: number
- ) {
- super(config, tabID)
- }
-
- protected createNextState(
- config: SessionStateConfig,
- params: CreateNextStateParams,
- StateClass?: new (
- config: SessionStateConfig,
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- references: CodeReference[],
- tabID: string,
- currentIteration: number,
- remainingIterations?: number,
- totalIterations?: number,
- uploadHistory?: UploadHistory,
- tokenSource?: vscode.CancellationTokenSource,
- currentCodeGenerationId?: string,
- codeGenerationId?: string
- ) => SessionState
- ): SessionState {
- return new StateClass!(
- config,
- params.filePaths,
- params.deletedFiles,
- params.references,
- this.tabID,
- params.currentIteration,
- params.remainingIterations,
- params.totalIterations,
- params.uploadHistory,
- params.tokenSource,
- params.currentCodeGenerationId,
- params.codeGenerationId
- )
- }
-
- async interact(action: SessionStateAction): Promise {
- return telemetry.amazonq_codeGenerationInvoke.run(async (span) => {
- try {
- action.tokenSource?.token.onCancellationRequested(() => {
- this.isCancellationRequested = true
- if (action.tokenSource) {
- this.tokenSource = action.tokenSource
- }
- })
-
- span.record({
- amazonqConversationId: this.config.conversationId,
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
-
- action.telemetry.setGenerateCodeIteration(this.currentIteration)
- action.telemetry.setGenerateCodeLastInvocationTime()
-
- const codeGenerationId = randomUUID()
- await this.startCodeGeneration(action, codeGenerationId)
-
- const codeGeneration = await this.generateCode({
- messenger: action.messenger,
- fs: action.fs,
- codeGenerationId,
- telemetry: action.telemetry,
- workspaceFolders: this.config.workspaceFolders,
- action,
- })
-
- if (codeGeneration && !action.tokenSource?.token.isCancellationRequested) {
- this.config.currentCodeGenerationId = codeGenerationId
- this.currentCodeGenerationId = codeGenerationId
- }
-
- this.filePaths = codeGeneration.newFiles
- this.deletedFiles = codeGeneration.deletedFiles
- this.references = codeGeneration.references
- this.codeGenerationRemainingIterationCount = codeGeneration.codeGenerationRemainingIterationCount
- this.codeGenerationTotalIterationCount = codeGeneration.codeGenerationTotalIterationCount
- this.currentIteration =
- this.codeGenerationRemainingIterationCount && this.codeGenerationTotalIterationCount
- ? this.codeGenerationTotalIterationCount - this.codeGenerationRemainingIterationCount
- : this.currentIteration + 1
-
- if (action.uploadHistory && !action.uploadHistory[codeGenerationId] && codeGenerationId) {
- action.uploadHistory[codeGenerationId] = {
- timestamp: Date.now(),
- uploadId: this.config.uploadId,
- filePaths: codeGeneration.newFiles,
- deletedFiles: codeGeneration.deletedFiles,
- tabId: this.tabID,
- }
- }
-
- action.telemetry.setAmazonqNumberOfReferences(this.references.length)
- action.telemetry.recordUserCodeGenerationTelemetry(span, this.conversationId)
-
- const nextState = this.createNextState(this.config, {
- filePaths: this.filePaths,
- deletedFiles: this.deletedFiles,
- references: this.references,
- currentIteration: this.currentIteration,
- remainingIterations: this.codeGenerationRemainingIterationCount,
- totalIterations: this.codeGenerationTotalIterationCount,
- uploadHistory: action.uploadHistory ? action.uploadHistory : {},
- tokenSource: this.tokenSource,
- currentCodeGenerationId: this.currentCodeGenerationId,
- codeGenerationId,
- })
-
- return {
- nextState,
- interaction: {},
- }
- } catch (e) {
- throw e instanceof ToolkitError
- ? e
- : ToolkitError.chain(e, 'Server side error', { code: 'UnhandledCodeGenServerSideError' })
- }
- })
- }
-
- protected abstract startCodeGeneration(action: SessionStateAction, codeGenerationId: string): Promise
-}
diff --git a/packages/core/src/amazonq/util/files.ts b/packages/core/src/amazonq/util/files.ts
deleted file mode 100644
index afa0b674928..00000000000
--- a/packages/core/src/amazonq/util/files.ts
+++ /dev/null
@@ -1,301 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as path from 'path'
-import {
- collectFiles,
- CollectFilesFilter,
- defaultExcludePatterns,
- getWorkspaceFoldersByPrefixes,
-} from '../../shared/utilities/workspaceUtils'
-
-import { PrepareRepoFailedError } from '../../amazonqFeatureDev/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { maxFileSizeBytes } from '../../amazonqFeatureDev/limits'
-import { CurrentWsFolders, DeletedFileInfo, NewFileInfo, NewFileZipContents } from '../../amazonqDoc/types'
-import { ContentLengthError, hasCode, ToolkitError } from '../../shared/errors'
-import { AmazonqCreateUpload, Span, telemetry as amznTelemetry, telemetry } from '../../shared/telemetry/telemetry'
-import { maxRepoSizeBytes } from '../../amazonqFeatureDev/constants'
-import { isCodeFile } from '../../shared/filetypes'
-import { fs } from '../../shared/fs/fs'
-import { VirtualFileSystem } from '../../shared/virtualFilesystem'
-import { VirtualMemoryFile } from '../../shared/virtualMemoryFile'
-import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings'
-import { ZipStream } from '../../shared/utilities/zipStream'
-import { isPresent } from '../../shared/utilities/collectionUtils'
-import { AuthUtil } from '../../codewhisperer/util/authUtil'
-import { TelemetryHelper } from '../util/telemetryHelper'
-
-export const SvgFileExtension = '.svg'
-
-export async function checkForDevFile(root: string) {
- const devFilePath = root + '/devfile.yaml'
- const hasDevFile = await fs.existsFile(devFilePath)
- return hasDevFile
-}
-
-function isInfraDiagramFile(relativePath: string) {
- return (
- relativePath.toLowerCase().endsWith(path.join('docs', 'infra.dot')) ||
- relativePath.toLowerCase().endsWith(path.join('docs', 'infra.svg'))
- )
-}
-
-export type PrepareRepoDataOptions = {
- telemetry?: TelemetryHelper
- zip?: ZipStream
- isIncludeInfraDiagram?: boolean
-}
-
-/**
- * given the root path of the repo it zips its files in memory and generates a checksum for it.
- */
-export async function prepareRepoData(
- repoRootPaths: string[],
- workspaceFolders: CurrentWsFolders,
- span: Span,
- options?: PrepareRepoDataOptions
-) {
- try {
- const telemetry = options?.telemetry
- const isIncludeInfraDiagram = options?.isIncludeInfraDiagram ?? false
- const zip = options?.zip ?? new ZipStream()
-
- const autoBuildSetting = CodeWhispererSettings.instance.getAutoBuildSetting()
- const useAutoBuildFeature = autoBuildSetting[repoRootPaths[0]] ?? false
- const excludePatterns: string[] = []
- let filterFn: CollectFilesFilter | undefined = undefined
-
- // We only respect gitignore file rules if useAutoBuildFeature is on, this is to avoid dropping necessary files for building the code (e.g. png files imported in js code)
- if (!useAutoBuildFeature) {
- if (isIncludeInfraDiagram) {
- // ensure svg is not filtered out by files search
- excludePatterns.push(...defaultExcludePatterns.filter((p) => !p.endsWith(SvgFileExtension)))
- // ensure only infra diagram is included from all svg files
- filterFn = (relativePath: string) => {
- if (!relativePath.toLowerCase().endsWith(SvgFileExtension)) {
- return false
- }
- return !isInfraDiagramFile(relativePath)
- }
- } else {
- excludePatterns.push(...defaultExcludePatterns)
- }
- }
-
- const files = await collectFiles(repoRootPaths, workspaceFolders, {
- maxTotalSizeBytes: maxRepoSizeBytes,
- excludeByGitIgnore: true,
- excludePatterns: excludePatterns,
- filterFn: filterFn,
- })
-
- let totalBytes = 0
- const ignoredExtensionMap = new Map()
- for (const file of files) {
- let fileSize
- try {
- fileSize = (await fs.stat(file.fileUri)).size
- } catch (error) {
- if (hasCode(error) && error.code === 'ENOENT') {
- // No-op: Skip if file does not exist
- continue
- }
- throw error
- }
- const isCodeFile_ = isCodeFile(file.relativeFilePath)
- const isDevFile = file.relativeFilePath === 'devfile.yaml'
- const isInfraDiagramFileExt = isInfraDiagramFile(file.relativeFilePath)
-
- let isExcludeFile = fileSize >= maxFileSizeBytes
- // When useAutoBuildFeature is on, only respect the gitignore rules filtered earlier and apply the size limit
- if (!isExcludeFile && !useAutoBuildFeature) {
- isExcludeFile = isDevFile || (!isCodeFile_ && (!isIncludeInfraDiagram || !isInfraDiagramFileExt))
- }
-
- if (isExcludeFile) {
- if (!isCodeFile_) {
- const re = /(?:\.([^.]+))?$/
- const extensionArray = re.exec(file.relativeFilePath)
- const extension = extensionArray?.length ? extensionArray[1] : undefined
- if (extension) {
- const currentCount = ignoredExtensionMap.get(extension)
-
- ignoredExtensionMap.set(extension, (currentCount ?? 0) + 1)
- }
- }
- continue
- }
-
- totalBytes += fileSize
- // Paths in zip should be POSIX compliant regardless of OS
- // Reference: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
- const posixPath = file.zipFilePath.split(path.sep).join(path.posix.sep)
-
- try {
- zip.writeFile(file.fileUri.fsPath, posixPath)
- } catch (error) {
- if (error instanceof Error && error.message.includes('File not found')) {
- // No-op: Skip if file was deleted or does not exist
- // Reference: https://github.com/cthackers/adm-zip/blob/1cd32f7e0ad3c540142a76609bb538a5cda2292f/adm-zip.js#L296-L321
- continue
- }
- throw error
- }
- }
-
- const iterator = ignoredExtensionMap.entries()
-
- for (let i = 0; i < ignoredExtensionMap.size; i++) {
- const iteratorValue = iterator.next().value
- if (iteratorValue) {
- const [key, value] = iteratorValue
- await amznTelemetry.amazonq_bundleExtensionIgnored.run(async (bundleSpan) => {
- const event = {
- filenameExt: key,
- count: value,
- }
-
- bundleSpan.record(event)
- })
- }
- }
-
- if (telemetry) {
- telemetry.setRepositorySize(totalBytes)
- }
-
- span.record({ amazonqRepositorySize: totalBytes })
- const zipResult = await zip.finalize()
-
- const zipFileBuffer = zipResult.streamBuffer.getContents() || Buffer.from('')
- return {
- zipFileBuffer,
- zipFileChecksum: zipResult.hash,
- }
- } catch (error) {
- getLogger().debug(`Failed to prepare repo: ${error}`)
- if (error instanceof ToolkitError && error.code === 'ContentLengthError') {
- throw new ContentLengthError(error.message)
- }
- throw new PrepareRepoFailedError()
- }
-}
-
-/**
- * gets the absolute path from a zip path
- * @param zipFilePath the path in the zip file
- * @param workspacesByPrefix the workspaces with generated prefixes
- * @param workspaceFolders all workspace folders
- * @returns all possible path info
- */
-export function getPathsFromZipFilePath(
- zipFilePath: string,
- workspacesByPrefix: { [prefix: string]: vscode.WorkspaceFolder } | undefined,
- workspaceFolders: CurrentWsFolders
-): {
- absolutePath: string
- relativePath: string
- workspaceFolder: vscode.WorkspaceFolder
-} {
- // when there is just a single workspace folder, there is no prefixing
- if (workspacesByPrefix === undefined) {
- return {
- absolutePath: path.join(workspaceFolders[0].uri.fsPath, zipFilePath),
- relativePath: zipFilePath,
- workspaceFolder: workspaceFolders[0],
- }
- }
- // otherwise the first part of the zipPath is the prefix
- const prefix = zipFilePath.substring(0, zipFilePath.indexOf(path.sep))
- const workspaceFolder =
- workspacesByPrefix[prefix] ??
- (workspacesByPrefix[Object.values(workspacesByPrefix).find((val) => val.index === 0)?.name ?? ''] || undefined)
- if (workspaceFolder === undefined) {
- throw new ToolkitError(`Could not find workspace folder for prefix ${prefix}`)
- }
- return {
- absolutePath: path.join(workspaceFolder.uri.fsPath, zipFilePath.substring(prefix.length + 1)),
- relativePath: zipFilePath.substring(prefix.length + 1),
- workspaceFolder,
- }
-}
-
-export function getDeletedFileInfos(deletedFiles: string[], workspaceFolders: CurrentWsFolders): DeletedFileInfo[] {
- const workspaceFolderPrefixes = getWorkspaceFoldersByPrefixes(workspaceFolders)
- return deletedFiles
- .map((deletedFilePath) => {
- const prefix =
- workspaceFolderPrefixes === undefined
- ? ''
- : deletedFilePath.substring(0, deletedFilePath.indexOf(path.sep))
- const folder = workspaceFolderPrefixes === undefined ? workspaceFolders[0] : workspaceFolderPrefixes[prefix]
- if (folder === undefined) {
- getLogger().error(`No workspace folder found for file: ${deletedFilePath} and prefix: ${prefix}`)
- return undefined
- }
- const prefixLength = workspaceFolderPrefixes === undefined ? 0 : prefix.length + 1
- return {
- zipFilePath: deletedFilePath,
- workspaceFolder: folder,
- relativePath: deletedFilePath.substring(prefixLength),
- rejected: false,
- changeApplied: false,
- }
- })
- .filter(isPresent)
-}
-
-export function registerNewFiles(
- fs: VirtualFileSystem,
- newFileContents: NewFileZipContents[],
- uploadId: string,
- workspaceFolders: CurrentWsFolders,
- conversationId: string,
- scheme: string
-): NewFileInfo[] {
- const result: NewFileInfo[] = []
- const workspaceFolderPrefixes = getWorkspaceFoldersByPrefixes(workspaceFolders)
- for (const { zipFilePath, fileContent } of newFileContents) {
- const encoder = new TextEncoder()
- const contents = encoder.encode(fileContent)
- const generationFilePath = path.join(uploadId, zipFilePath)
- const uri = vscode.Uri.from({ scheme, path: generationFilePath })
- fs.registerProvider(uri, new VirtualMemoryFile(contents))
- const prefix =
- workspaceFolderPrefixes === undefined ? '' : zipFilePath.substring(0, zipFilePath.indexOf(path.sep))
- const folder =
- workspaceFolderPrefixes === undefined
- ? workspaceFolders[0]
- : (workspaceFolderPrefixes[prefix] ??
- workspaceFolderPrefixes[
- Object.values(workspaceFolderPrefixes).find((val) => val.index === 0)?.name ?? ''
- ])
- if (folder === undefined) {
- telemetry.toolkit_trackScenario.emit({
- count: 1,
- amazonqConversationId: conversationId,
- credentialStartUrl: AuthUtil.instance.startUrl,
- scenario: 'wsOrphanedDocuments',
- })
- getLogger().error(`No workspace folder found for file: ${zipFilePath} and prefix: ${prefix}`)
- continue
- }
- result.push({
- zipFilePath,
- fileContent,
- virtualMemoryUri: uri,
- workspaceFolder: folder,
- relativePath: zipFilePath.substring(
- workspaceFolderPrefixes === undefined ? 0 : prefix.length > 0 ? prefix.length + 1 : 0
- ),
- rejected: false,
- changeApplied: false,
- })
- }
-
- return result
-}
diff --git a/packages/core/src/amazonq/util/upload.ts b/packages/core/src/amazonq/util/upload.ts
index bd4ff26cc45..92e88fbea0e 100644
--- a/packages/core/src/amazonq/util/upload.ts
+++ b/packages/core/src/amazonq/util/upload.ts
@@ -5,11 +5,10 @@
import request, { RequestError } from '../../shared/request'
import { getLogger } from '../../shared/logger/logger'
-import { featureName } from '../../amazonqFeatureDev/constants'
-import { UploadCodeError, UploadURLExpired } from '../../amazonqFeatureDev/errors'
-import { ToolkitError } from '../../shared/errors'
+import { ToolkitError, UploadCodeError, UploadURLExpired } from '../../shared/errors'
import { i18n } from '../../shared/i18n-helper'
+import { featureName } from '../../shared/constants'
/**
* uploadCode
diff --git a/packages/core/src/amazonqDoc/app.ts b/packages/core/src/amazonqDoc/app.ts
deleted file mode 100644
index 52985b82a00..00000000000
--- a/packages/core/src/amazonqDoc/app.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ChatControllerEventEmitters, DocController } from './controllers/chat/controller'
-import { AmazonQAppInitContext } from '../amazonq/apps/initContext'
-import { MessageListener } from '../amazonq/messages/messageListener'
-import { fromQueryToParameters } from '../shared/utilities/uriUtils'
-import { getLogger } from '../shared/logger/logger'
-import { AuthUtil } from '../codewhisperer/util/authUtil'
-import { debounce } from 'lodash'
-import { DocChatSessionStorage } from './storages/chatSession'
-import { UIMessageListener } from './views/actions/uiMessageListener'
-import globals from '../shared/extensionGlobals'
-import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages'
-import { docChat, docScheme } from './constants'
-import { TabIdNotFoundError } from '../amazonqFeatureDev/errors'
-import { DocMessenger } from './messenger'
-
-export function init(appContext: AmazonQAppInitContext) {
- const docChatControllerEventEmitters: ChatControllerEventEmitters = {
- processHumanChatMessage: new vscode.EventEmitter(),
- followUpClicked: new vscode.EventEmitter(),
- openDiff: new vscode.EventEmitter(),
- processChatItemVotedMessage: new vscode.EventEmitter(),
- stopResponse: new vscode.EventEmitter(),
- tabOpened: new vscode.EventEmitter(),
- processChatItemFeedbackMessage: new vscode.EventEmitter(),
- tabClosed: new vscode.EventEmitter(),
- authClicked: new vscode.EventEmitter(),
- formActionClicked: new vscode.EventEmitter(),
- processResponseBodyLinkClick: new vscode.EventEmitter(),
- insertCodeAtPositionClicked: new vscode.EventEmitter(),
- fileClicked: new vscode.EventEmitter(),
- }
-
- const messenger = new DocMessenger(
- new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()),
- docChat
- )
- const sessionStorage = new DocChatSessionStorage(messenger)
-
- new DocController(
- docChatControllerEventEmitters,
- messenger,
- sessionStorage,
- appContext.onDidChangeAmazonQVisibility.event
- )
-
- const docProvider = new (class implements vscode.TextDocumentContentProvider {
- async provideTextDocumentContent(uri: vscode.Uri): Promise {
- const params = fromQueryToParameters(uri.query)
-
- const tabID = params.get('tabID')
- if (!tabID) {
- getLogger().error(`Unable to find tabID from ${uri.toString()}`)
- throw new TabIdNotFoundError()
- }
-
- const session = await sessionStorage.getSession(tabID)
- const content = await session.config.fs.readFile(uri)
- const decodedContent = new TextDecoder().decode(content)
- return decodedContent
- }
- })()
-
- const textDocumentProvider = vscode.workspace.registerTextDocumentContentProvider(docScheme, docProvider)
-
- globals.context.subscriptions.push(textDocumentProvider)
-
- const docChatUIInputEventEmitter = new vscode.EventEmitter()
-
- new UIMessageListener({
- chatControllerEventEmitters: docChatControllerEventEmitters,
- webViewMessageListener: new MessageListener(docChatUIInputEventEmitter),
- })
-
- const debouncedEvent = debounce(async () => {
- const authenticated = (await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'
- let authenticatingSessionIDs: string[] = []
- if (authenticated) {
- const authenticatingSessions = sessionStorage.getAuthenticatingSessions()
-
- authenticatingSessionIDs = authenticatingSessions.map((session: any) => session.tabID)
-
- // We've already authenticated these sessions
- for (const session of authenticatingSessions) {
- session.isAuthenticating = false
- }
- }
-
- messenger.sendAuthenticationUpdate(authenticated, authenticatingSessionIDs)
- }, 500)
-
- AuthUtil.instance.secondaryAuth.onDidChangeActiveConnection(() => {
- return debouncedEvent()
- })
- AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => {
- return debouncedEvent()
- })
-}
diff --git a/packages/core/src/amazonqDoc/constants.ts b/packages/core/src/amazonqDoc/constants.ts
deleted file mode 100644
index 7b57e7c2ce9..00000000000
--- a/packages/core/src/amazonqDoc/constants.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { MynahIcons, Status } from '@aws/mynah-ui'
-import { FollowUpTypes } from '../amazonq/commons/types'
-import { NewFileInfo } from './types'
-import { i18n } from '../shared/i18n-helper'
-
-// For uniquely identifiying which chat messages should be routed to Doc
-export const docChat = 'docChat'
-
-export const docScheme = 'aws-doc'
-
-export const featureName = 'Amazon Q Doc Generation'
-
-export function getFileSummaryPercentage(input: string): number {
- // Split the input string by newline characters
- const lines = input.split('\n')
-
- // Find the line containing "summarized:"
- const summaryLine = lines.find((line) => line.includes('summarized:'))
-
- // If the line is not found, return null
- if (!summaryLine) {
- return -1
- }
-
- // Extract the numbers from the summary line
- const [summarized, total] = summaryLine.split(':')[1].trim().split(' of ').map(Number)
-
- // Calculate the percentage
- const percentage = (summarized / total) * 100
-
- return percentage
-}
-
-const checkIcons = {
- wait: '☐',
- current: '☐',
- done: '☑',
-}
-
-const getIconForStep = (targetStep: number, currentStep: number) => {
- return currentStep === targetStep
- ? checkIcons.current
- : currentStep > targetStep
- ? checkIcons.done
- : checkIcons.wait
-}
-
-export enum DocGenerationStep {
- UPLOAD_TO_S3,
- SUMMARIZING_FILES,
- GENERATING_ARTIFACTS,
-}
-
-export const docGenerationProgressMessage = (currentStep: DocGenerationStep, mode: Mode) => `
-${mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.creating') : i18n('AWS.amazonq.doc.answer.updating')}
-
-${getIconForStep(DocGenerationStep.UPLOAD_TO_S3, currentStep)} ${i18n('AWS.amazonq.doc.answer.scanning')}
-
-${getIconForStep(DocGenerationStep.SUMMARIZING_FILES, currentStep)} ${i18n('AWS.amazonq.doc.answer.summarizing')}
-
-${getIconForStep(DocGenerationStep.GENERATING_ARTIFACTS, currentStep)} ${i18n('AWS.amazonq.doc.answer.generating')}
-
-
-`
-
-export const docGenerationSuccessMessage = (mode: Mode) =>
- mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')
-
-export const docRejectConfirmation = 'Your changes have been discarded.'
-
-export const FolderSelectorFollowUps = [
- {
- icon: 'ok' as MynahIcons,
- pillText: 'Yes',
- prompt: 'Yes',
- status: 'success' as Status,
- type: FollowUpTypes.ProceedFolderSelection,
- },
- {
- icon: 'refresh' as MynahIcons,
- pillText: 'Change folder',
- prompt: 'Change folder',
- status: 'info' as Status,
- type: FollowUpTypes.ChooseFolder,
- },
- {
- icon: 'cancel' as MynahIcons,
- pillText: 'Cancel',
- prompt: 'Cancel',
- status: 'error' as Status,
- type: FollowUpTypes.CancelFolderSelection,
- },
-]
-
-export const CodeChangeFollowUps = [
- {
- pillText: i18n('AWS.amazonq.doc.pillText.accept'),
- prompt: i18n('AWS.amazonq.doc.pillText.accept'),
- type: FollowUpTypes.AcceptChanges,
- icon: 'ok' as MynahIcons,
- status: 'success' as Status,
- },
- {
- pillText: i18n('AWS.amazonq.doc.pillText.makeChanges'),
- prompt: i18n('AWS.amazonq.doc.pillText.makeChanges'),
- type: FollowUpTypes.MakeChanges,
- icon: 'refresh' as MynahIcons,
- status: 'info' as Status,
- },
- {
- pillText: i18n('AWS.amazonq.doc.pillText.reject'),
- prompt: i18n('AWS.amazonq.doc.pillText.reject'),
- type: FollowUpTypes.RejectChanges,
- icon: 'cancel' as MynahIcons,
- status: 'error' as Status,
- },
-]
-
-export const NewSessionFollowUps = [
- {
- pillText: i18n('AWS.amazonq.doc.pillText.newTask'),
- type: FollowUpTypes.NewTask,
- status: 'info' as Status,
- },
- {
- pillText: i18n('AWS.amazonq.doc.pillText.closeSession'),
- type: FollowUpTypes.CloseSession,
- status: 'info' as Status,
- },
-]
-
-export const SynchronizeDocumentation = {
- pillText: i18n('AWS.amazonq.doc.pillText.update'),
- prompt: i18n('AWS.amazonq.doc.pillText.update'),
- type: FollowUpTypes.SynchronizeDocumentation,
-}
-
-export const EditDocumentation = {
- pillText: i18n('AWS.amazonq.doc.pillText.makeChange'),
- prompt: i18n('AWS.amazonq.doc.pillText.makeChange'),
- type: FollowUpTypes.EditDocumentation,
-}
-
-export enum Mode {
- NONE = 'None',
- CREATE = 'Create',
- SYNC = 'Sync',
- EDIT = 'Edit',
-}
-
-/**
- *
- * @param paths file paths
- * @returns the path to a README.md, or undefined if none exist
- */
-export const findReadmePath = (paths?: NewFileInfo[]) => {
- return paths?.find((path) => /readme\.md$/i.test(path.relativePath))
-}
diff --git a/packages/core/src/amazonqDoc/controllers/chat/controller.ts b/packages/core/src/amazonqDoc/controllers/chat/controller.ts
deleted file mode 100644
index ab6045e75ce..00000000000
--- a/packages/core/src/amazonqDoc/controllers/chat/controller.ts
+++ /dev/null
@@ -1,715 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as vscode from 'vscode'
-import { EventEmitter } from 'vscode'
-
-import {
- DocGenerationStep,
- EditDocumentation,
- FolderSelectorFollowUps,
- Mode,
- NewSessionFollowUps,
- SynchronizeDocumentation,
- CodeChangeFollowUps,
- docScheme,
- featureName,
- findReadmePath,
-} from '../../constants'
-import { AuthUtil } from '../../../codewhisperer/util/authUtil'
-import { getLogger } from '../../../shared/logger/logger'
-
-import { Session } from '../../session/session'
-import { i18n } from '../../../shared/i18n-helper'
-import path from 'path'
-import { createSingleFileDialog } from '../../../shared/ui/common/openDialog'
-
-import {
- MonthlyConversationLimitError,
- SelectedFolderNotInWorkspaceFolderError,
- WorkspaceFolderNotFoundError,
- createUserFacingErrorMessage,
- getMetricResult,
-} from '../../../amazonqFeatureDev/errors'
-import { BaseChatSessionStorage } from '../../../amazonq/commons/baseChatStorage'
-import { DocMessenger } from '../../messenger'
-import { AuthController } from '../../../amazonq/auth/controller'
-import { openUrl } from '../../../shared/utilities/vsCodeUtils'
-import { createAmazonQUri, openDeletedDiff, openDiff } from '../../../amazonq/commons/diff'
-import {
- getWorkspaceFoldersByPrefixes,
- getWorkspaceRelativePath,
- isMultiRootWorkspace,
-} from '../../../shared/utilities/workspaceUtils'
-import { getPathsFromZipFilePath, SvgFileExtension } from '../../../amazonq/util/files'
-import { FollowUpTypes } from '../../../amazonq/commons/types'
-import { DocGenerationTask, DocGenerationTasks } from '../docGenerationTask'
-import { normalize } from '../../../shared/utilities/pathUtils'
-import { DevPhase, MetricDataOperationName, MetricDataResult } from '../../types'
-
-export interface ChatControllerEventEmitters {
- readonly processHumanChatMessage: EventEmitter
- readonly followUpClicked: EventEmitter
- readonly openDiff: EventEmitter
- readonly stopResponse: EventEmitter
- readonly tabOpened: EventEmitter
- readonly tabClosed: EventEmitter
- readonly processChatItemVotedMessage: EventEmitter
- readonly processChatItemFeedbackMessage: EventEmitter
- readonly authClicked: EventEmitter
- readonly processResponseBodyLinkClick: EventEmitter
- readonly insertCodeAtPositionClicked: EventEmitter
- readonly fileClicked: EventEmitter
- readonly formActionClicked: EventEmitter
-}
-
-export class DocController {
- private readonly scheme = docScheme
- private readonly messenger: DocMessenger
- private readonly sessionStorage: BaseChatSessionStorage
- private authController: AuthController
- private docGenerationTasks: DocGenerationTasks
-
- public constructor(
- private readonly chatControllerMessageListeners: ChatControllerEventEmitters,
- messenger: DocMessenger,
- sessionStorage: BaseChatSessionStorage,
- _onDidChangeAmazonQVisibility: vscode.Event
- ) {
- this.messenger = messenger
- this.sessionStorage = sessionStorage
- this.authController = new AuthController()
- this.docGenerationTasks = new DocGenerationTasks()
-
- this.chatControllerMessageListeners.processHumanChatMessage.event((data) => {
- this.processUserChatMessage(data).catch((e) => {
- getLogger().error('processUserChatMessage failed: %s', (e as Error).message)
- })
- })
- this.chatControllerMessageListeners.formActionClicked.event((data) => {
- return this.formActionClicked(data)
- })
-
- this.initializeFollowUps()
-
- this.chatControllerMessageListeners.stopResponse.event((data) => {
- return this.stopResponse(data)
- })
- this.chatControllerMessageListeners.tabOpened.event((data) => {
- return this.tabOpened(data)
- })
- this.chatControllerMessageListeners.tabClosed.event((data) => {
- this.tabClosed(data)
- })
- this.chatControllerMessageListeners.authClicked.event((data) => {
- this.authClicked(data)
- })
- this.chatControllerMessageListeners.processResponseBodyLinkClick.event((data) => {
- this.processLink(data)
- })
- this.chatControllerMessageListeners.fileClicked.event(async (data) => {
- return await this.fileClicked(data)
- })
- this.chatControllerMessageListeners.openDiff.event(async (data) => {
- return await this.openDiff(data)
- })
- AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => {
- this.sessionStorage.deleteAllSessions()
- })
- }
-
- /** Prompts user to choose a folder in current workspace for README creation/update.
- * After user chooses a folder, displays confirmation message to user with selected path.
- *
- */
- private async folderSelector(data: any) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data.tabID,
- message: i18n('AWS.amazonq.doc.answer.chooseFolder'),
- disableChatInput: true,
- })
-
- const uri = await createSingleFileDialog({
- canSelectFolders: true,
- canSelectFiles: false,
- }).prompt()
-
- const retryFollowUps = FolderSelectorFollowUps.filter(
- (followUp) => followUp.type !== FollowUpTypes.ProceedFolderSelection
- )
-
- if (!(uri instanceof vscode.Uri)) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data.tabID,
- message: i18n('AWS.amazonq.doc.error.noFolderSelected'),
- followUps: retryFollowUps,
- disableChatInput: true,
- })
- // Check that selected folder is a subfolder of the current workspace
- } else if (!vscode.workspace.getWorkspaceFolder(uri)) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data.tabID,
- message: new SelectedFolderNotInWorkspaceFolderError().message,
- followUps: retryFollowUps,
- disableChatInput: true,
- })
- } else {
- let displayPath = ''
- const relativePath = getWorkspaceRelativePath(uri.fsPath)
- const docGenerationTask = this.docGenerationTasks.getTask(data.tabID)
- if (relativePath) {
- // Display path should always include workspace folder name
- displayPath = path.join(relativePath.workspaceFolder.name, relativePath.relativePath)
- // Only include workspace folder name in API call if multi-root workspace
- docGenerationTask.folderPath = normalize(
- isMultiRootWorkspace() ? displayPath : relativePath.relativePath
- )
-
- if (!relativePath.relativePath) {
- docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE'
- } else {
- docGenerationTask.folderLevel = 'SUB_FOLDER'
- }
- }
-
- this.messenger.sendFolderConfirmationMessage(
- data.tabID,
- docGenerationTask.mode === Mode.CREATE
- ? i18n('AWS.amazonq.doc.answer.createReadme')
- : i18n('AWS.amazonq.doc.answer.updateReadme'),
- displayPath,
- FolderSelectorFollowUps
- )
- this.messenger.sendChatInputEnabled(data.tabID, false)
- }
- }
-
- private async openDiff(message: any) {
- const tabId: string = message.tabID
- const codeGenerationId: string = message.messageId
- const zipFilePath: string = message.filePath
- const session = await this.sessionStorage.getSession(tabId)
-
- const workspacePrefixMapping = getWorkspaceFoldersByPrefixes(session.config.workspaceFolders)
- const pathInfos = getPathsFromZipFilePath(zipFilePath, workspacePrefixMapping, session.config.workspaceFolders)
-
- const extension = path.parse(message.filePath).ext
- // Only open diffs on files, not directories
- if (extension) {
- if (message.deleted) {
- const name = path.basename(pathInfos.relativePath)
- await openDeletedDiff(pathInfos.absolutePath, name, tabId, this.scheme)
- } else {
- let uploadId = session.uploadId
- if (session?.state?.uploadHistory && session.state.uploadHistory[codeGenerationId]) {
- uploadId = session?.state?.uploadHistory[codeGenerationId].uploadId
- }
- const rightPath = path.join(uploadId, zipFilePath)
- if (rightPath.toLowerCase().endsWith(SvgFileExtension)) {
- const rightPathUri = createAmazonQUri(rightPath, tabId, this.scheme)
- const infraDiagramContent = await vscode.workspace.openTextDocument(rightPathUri)
- await vscode.window.showTextDocument(infraDiagramContent)
- } else {
- await openDiff(pathInfos.absolutePath, rightPath, tabId, this.scheme)
- }
- }
- }
- }
-
- private initializeFollowUps(): void {
- this.chatControllerMessageListeners.followUpClicked.event(async (data) => {
- const session: Session = await this.sessionStorage.getSession(data.tabID)
- const docGenerationTask = this.docGenerationTasks.getTask(data.tabID)
-
- const workspaceFolders = vscode.workspace.workspaceFolders
- if (workspaceFolders === undefined || workspaceFolders.length === 0) {
- return
- }
-
- const workspaceFolderName = vscode.workspace.workspaceFolders?.[0].name || ''
-
- const authState = await AuthUtil.instance.getChatAuthState()
-
- if (authState.amazonQ !== 'connected') {
- await this.messenger.sendAuthNeededExceptionMessage(authState, data.tabID)
- session.isAuthenticating = true
- return
- }
-
- const sendFolderConfirmationMessage = (message: string) => {
- this.messenger.sendFolderConfirmationMessage(
- data.tabID,
- message,
- workspaceFolderName,
- FolderSelectorFollowUps
- )
- }
-
- switch (data.followUp.type) {
- case FollowUpTypes.Retry:
- if (docGenerationTask.mode === Mode.EDIT) {
- this.enableUserInput(data?.tabID)
- } else {
- await this.tabOpened(data)
- }
- break
- case FollowUpTypes.NewTask:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data?.tabID,
- message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'),
- disableChatInput: true,
- })
- return this.newTask(data)
- case FollowUpTypes.CloseSession:
- return this.closeSession(data)
- case FollowUpTypes.CreateDocumentation:
- docGenerationTask.interactionType = 'GENERATE_README'
- docGenerationTask.mode = Mode.CREATE
- sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.createReadme'))
- break
- case FollowUpTypes.ChooseFolder:
- await this.folderSelector(data)
- break
- case FollowUpTypes.SynchronizeDocumentation:
- docGenerationTask.mode = Mode.SYNC
- sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme'))
- break
- case FollowUpTypes.UpdateDocumentation:
- docGenerationTask.interactionType = 'UPDATE_README'
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data?.tabID,
- followUps: [SynchronizeDocumentation, EditDocumentation],
- disableChatInput: true,
- })
- break
- case FollowUpTypes.EditDocumentation:
- docGenerationTask.interactionType = 'EDIT_README'
- docGenerationTask.mode = Mode.EDIT
- sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme'))
- break
- case FollowUpTypes.MakeChanges:
- docGenerationTask.mode = Mode.EDIT
- this.enableUserInput(data.tabID)
- break
- case FollowUpTypes.AcceptChanges:
- docGenerationTask.userDecision = 'ACCEPT'
- await this.sendDocAcceptanceEvent(data)
- await this.insertCode(data)
- return
- case FollowUpTypes.RejectChanges:
- docGenerationTask.userDecision = 'REJECT'
- await this.sendDocAcceptanceEvent(data)
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data?.tabID,
- disableChatInput: true,
- message: 'Your changes have been discarded.',
- followUps: NewSessionFollowUps,
- })
- break
- case FollowUpTypes.ProceedFolderSelection:
- // If a user did not change the folder in a multi-root workspace, default to the first workspace folder
- if (docGenerationTask.folderPath === '' && isMultiRootWorkspace()) {
- docGenerationTask.folderPath = workspaceFolderName
- }
- if (docGenerationTask.mode === Mode.EDIT) {
- this.enableUserInput(data.tabID)
- } else {
- await this.generateDocumentation(
- {
- ...data,
- message:
- docGenerationTask.mode === Mode.CREATE
- ? 'Create documentation for a specific folder'
- : 'Sync documentation',
- },
- session,
- docGenerationTask
- )
- }
- break
- case FollowUpTypes.CancelFolderSelection:
- docGenerationTask.reset()
- return this.tabOpened(data)
- }
- })
- }
-
- private enableUserInput(tabID: string) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: i18n('AWS.amazonq.doc.answer.editReadme'),
- })
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.doc.placeholder.editReadme'))
- this.messenger.sendChatInputEnabled(tabID, true)
- }
-
- private async fileClicked(message: any) {
- const tabId: string = message.tabID
- const messageId = message.messageId
- const filePathToUpdate: string = message.filePath
-
- const session = await this.sessionStorage.getSession(tabId)
- const filePathIndex = (session.state.filePaths ?? []).findIndex((obj) => obj.relativePath === filePathToUpdate)
- if (filePathIndex !== -1 && session.state.filePaths) {
- session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected
- }
- const deletedFilePathIndex = (session.state.deletedFiles ?? []).findIndex(
- (obj) => obj.relativePath === filePathToUpdate
- )
- if (deletedFilePathIndex !== -1 && session.state.deletedFiles) {
- session.state.deletedFiles[deletedFilePathIndex].rejected =
- !session.state.deletedFiles[deletedFilePathIndex].rejected
- }
-
- await session.updateFilesPaths(
- tabId,
- session.state.filePaths ?? [],
- session.state.deletedFiles ?? [],
- messageId,
- true
- )
- }
-
- private async formActionClicked(message: any) {
- switch (message.action) {
- case 'cancel-doc-generation':
- // eslint-disable-next-line unicorn/no-null
- await this.stopResponse(message)
-
- break
- }
- }
-
- private async newTask(message: any) {
- // Old session for the tab is ending, delete it so we can create a new one for the message id
-
- this.docGenerationTasks.deleteTask(message.tabID)
- this.sessionStorage.deleteSession(message.tabID)
-
- // Re-run the opening flow, where we check auth + create a session
- await this.tabOpened(message)
- }
-
- private async closeSession(message: any) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: i18n('AWS.amazonq.featureDev.answer.sessionClosed'),
- disableChatInput: true,
- })
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.sessionClosed'))
- this.messenger.sendChatInputEnabled(message.tabID, false)
- this.docGenerationTasks.deleteTask(message.tabID)
- }
-
- private processErrorChatMessage = (
- err: any,
- message: any,
- session: Session | undefined,
- docGenerationTask: DocGenerationTask
- ) => {
- const errorMessage = createUserFacingErrorMessage(`${err.cause?.message ?? err.message}`)
- // eslint-disable-next-line unicorn/no-null
- this.messenger.sendUpdatePromptProgress(message.tabID, null)
- if (err.constructor.name === MonthlyConversationLimitError.name) {
- this.messenger.sendMonthlyLimitError(message.tabID)
- } else {
- const enableUserInput = docGenerationTask.mode === Mode.EDIT && err.remainingIterations > 0
-
- this.messenger.sendErrorMessage(
- errorMessage,
- message.tabID,
- 0,
- session?.conversationIdUnsafe,
- false,
- enableUserInput
- )
- }
- }
-
- private async generateDocumentation(message: any, session: any, docGenerationTask: DocGenerationTask) {
- try {
- await this.onDocsGeneration(session, message.message, message.tabID, docGenerationTask)
- } catch (err: any) {
- this.processErrorChatMessage(err, message, session, docGenerationTask)
- }
- }
-
- private async processUserChatMessage(message: any) {
- if (message.message === undefined) {
- this.messenger.sendErrorMessage('chatMessage should be set', message.tabID, 0, undefined)
- return
- }
-
- /**
- * Don't attempt to process any chat messages when a workspace folder is not set.
- * When the tab is first opened we will throw an error and lock the chat if the workspace
- * folder is not found
- */
- const workspaceFolders = vscode.workspace.workspaceFolders
- if (workspaceFolders === undefined || workspaceFolders.length === 0) {
- return
- }
-
- const session: Session = await this.sessionStorage.getSession(message.tabID)
- const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
-
- try {
- getLogger().debug(`${featureName}: Processing message: ${message.message}`)
-
- const authState = await AuthUtil.instance.getChatAuthState()
- if (authState.amazonQ !== 'connected') {
- await this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
- session.isAuthenticating = true
- return
- }
-
- await this.generateDocumentation(message, session, docGenerationTask)
- } catch (err: any) {
- this.processErrorChatMessage(err, message, session, docGenerationTask)
- }
- }
-
- private async stopResponse(message: any) {
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration'),
- type: 'answer-part',
- tabID: message.tabID,
- })
- // eslint-disable-next-line unicorn/no-null
- this.messenger.sendUpdatePromptProgress(message.tabID, null)
- this.messenger.sendChatInputEnabled(message.tabID, false)
-
- const session = await this.sessionStorage.getSession(message.tabID)
- session.state.tokenSource?.cancel()
- }
-
- private async tabOpened(message: any) {
- let session: Session | undefined
- try {
- session = await this.sessionStorage.getSession(message.tabID)
- const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
- getLogger().debug(`${featureName}: Session created with id: ${session.tabID}`)
- docGenerationTask.folderPath = ''
- docGenerationTask.mode = Mode.NONE
-
- const authState = await AuthUtil.instance.getChatAuthState()
- if (authState.amazonQ !== 'connected') {
- void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
- session.isAuthenticating = true
- return
- }
- docGenerationTask.numberOfNavigations += 1
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- followUps: [
- {
- pillText: 'Create a README',
- prompt: 'Create a README',
- type: 'CreateDocumentation',
- },
- {
- pillText: 'Update an existing README',
- prompt: 'Update an existing README',
- type: 'UpdateDocumentation',
- },
- ],
- disableChatInput: true,
- })
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption'))
- } catch (err: any) {
- if (err instanceof WorkspaceFolderNotFoundError) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message,
- disableChatInput: true,
- })
- } else {
- this.messenger.sendErrorMessage(
- createUserFacingErrorMessage(err.message),
- message.tabID,
- 0,
- session?.conversationIdUnsafe
- )
- }
- }
- }
-
- private async openMarkdownPreview(readmePath: vscode.Uri) {
- await vscode.commands.executeCommand('vscode.open', readmePath)
- await vscode.commands.executeCommand('markdown.showPreview')
- }
-
- private async onDocsGeneration(
- session: Session,
- message: string,
- tabID: string,
- docGenerationTask: DocGenerationTask
- ) {
- this.messenger.sendDocProgress(tabID, DocGenerationStep.UPLOAD_TO_S3, 0, docGenerationTask.mode)
-
- await session.preloader(message)
-
- try {
- await session.sendDocMetricData(MetricDataOperationName.StartDocGeneration, MetricDataResult.Success)
- await session.send(message, docGenerationTask.mode, docGenerationTask.folderPath)
- const filePaths = session.state.filePaths ?? []
- const deletedFiles = session.state.deletedFiles ?? []
-
- // Only add the follow up accept/deny buttons when the tab hasn't been closed/request hasn't been cancelled
- if (session?.state.tokenSource?.token.isCancellationRequested) {
- return
- }
-
- if (filePaths.length === 0 && deletedFiles.length === 0) {
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.unableGenerateChanges'),
- type: 'answer',
- tabID: tabID,
- canBeVoted: true,
- disableChatInput: true,
- })
-
- return
- }
-
- this.messenger.sendCodeResult(
- filePaths,
- deletedFiles,
- session.state.references ?? [],
- tabID,
- session.uploadId,
- session.state.codeGenerationId ?? ''
- )
-
- // Automatically open the README diff
- const readmePath = findReadmePath(session.state.filePaths)
- if (readmePath) {
- await this.openDiff({ tabID, filePath: readmePath.zipFilePath })
- }
-
- const remainingIterations = session.state.codeGenerationRemainingIterationCount
- const totalIterations = session.state.codeGenerationTotalIterationCount
-
- if (remainingIterations !== undefined && totalIterations !== undefined) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: `${docGenerationTask.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${remainingIterations > 0 ? i18n('AWS.amazonq.doc.answer.codeResult') : i18n('AWS.amazonq.doc.answer.acceptOrReject')}`,
- disableChatInput: true,
- })
-
- this.messenger.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- disableChatInput: true,
- followUps:
- remainingIterations > 0
- ? CodeChangeFollowUps
- : CodeChangeFollowUps.filter((followUp) => followUp.type !== FollowUpTypes.MakeChanges),
- tabID: tabID,
- })
- }
- if (session?.state.phase === DevPhase.CODEGEN) {
- const docGenerationTask = this.docGenerationTasks.getTask(tabID)
- const { totalGeneratedChars, totalGeneratedLines, totalGeneratedFiles } =
- await session.countGeneratedContent(docGenerationTask.interactionType)
- docGenerationTask.conversationId = session.conversationId
- docGenerationTask.numberOfGeneratedChars = totalGeneratedChars
- docGenerationTask.numberOfGeneratedLines = totalGeneratedLines
- docGenerationTask.numberOfGeneratedFiles = totalGeneratedFiles
- const docGenerationEvent = docGenerationTask.docGenerationEventBase()
-
- await session.sendDocTelemetryEvent(docGenerationEvent, 'generation')
- }
- } catch (err: any) {
- getLogger().error(`${featureName}: Error during doc generation: ${err}`)
- await session.sendDocMetricData(MetricDataOperationName.EndDocGeneration, getMetricResult(err))
- throw err
- } finally {
- if (session?.state?.tokenSource?.token.isCancellationRequested) {
- await this.newTask({ tabID })
- } else {
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.doc.pillText.selectOption'))
-
- this.messenger.sendChatInputEnabled(tabID, false)
- }
- }
- await session.sendDocMetricData(MetricDataOperationName.EndDocGeneration, MetricDataResult.Success)
- }
-
- private authClicked(message: any) {
- this.authController.handleAuth(message.authType)
-
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: 'Follow instructions to re-authenticate ...',
- disableChatInput: true,
- })
- }
-
- private tabClosed(message: any) {
- this.sessionStorage.deleteSession(message.tabID)
- this.docGenerationTasks.deleteTask(message.tabID)
- }
-
- private async insertCode(message: any) {
- let session
- try {
- session = await this.sessionStorage.getSession(message.tabID)
-
- await session.insertChanges()
-
- const readmePath = findReadmePath(session.state.filePaths)
- if (readmePath) {
- await this.openMarkdownPreview(
- vscode.Uri.file(path.join(readmePath.workspaceFolder.uri.fsPath, readmePath.relativePath))
- )
- }
-
- this.messenger.sendAnswer({
- type: 'answer',
- disableChatInput: true,
- tabID: message.tabID,
- followUps: NewSessionFollowUps,
- })
-
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption'))
- } catch (err: any) {
- this.messenger.sendErrorMessage(
- createUserFacingErrorMessage(`Failed to insert code changes: ${err.message}`),
- message.tabID,
- 0,
- session?.conversationIdUnsafe
- )
- }
- }
- private async sendDocAcceptanceEvent(message: any) {
- const session = await this.sessionStorage.getSession(message.tabID)
- const docGenerationTask = this.docGenerationTasks.getTask(message.tabID)
- docGenerationTask.conversationId = session.conversationId
- const { totalAddedChars, totalAddedLines, totalAddedFiles } = await session.countAddedContent(
- docGenerationTask.interactionType
- )
- docGenerationTask.numberOfAddedChars = totalAddedChars
- docGenerationTask.numberOfAddedLines = totalAddedLines
- docGenerationTask.numberOfAddedFiles = totalAddedFiles
- const docAcceptanceEvent = docGenerationTask.docAcceptanceEventBase()
-
- await session.sendDocTelemetryEvent(docAcceptanceEvent, 'acceptance')
- }
- private processLink(message: any) {
- void openUrl(vscode.Uri.parse(message.link))
- }
-}
diff --git a/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts b/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts
deleted file mode 100644
index fe5dc25981c..00000000000
--- a/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
- DocFolderLevel,
- DocInteractionType,
- DocUserDecision,
- DocV2AcceptanceEvent,
- DocV2GenerationEvent,
-} from '../../codewhisperer/client/codewhispereruserclient'
-import { getLogger } from '../../shared/logger/logger'
-import { Mode } from '../constants'
-
-export class DocGenerationTasks {
- private tasks: Map = new Map()
-
- public getTask(tabId: string): DocGenerationTask {
- if (!this.tasks.has(tabId)) {
- this.tasks.set(tabId, new DocGenerationTask())
- }
- return this.tasks.get(tabId)!
- }
-
- public deleteTask(tabId: string): void {
- this.tasks.delete(tabId)
- }
-}
-
-export class DocGenerationTask {
- public mode: Mode = Mode.NONE
- public folderPath = ''
- // Telemetry fields
- public conversationId?: string
- public numberOfAddedChars?: number
- public numberOfAddedLines?: number
- public numberOfAddedFiles?: number
- public numberOfGeneratedChars?: number
- public numberOfGeneratedLines?: number
- public numberOfGeneratedFiles?: number
- public userDecision?: DocUserDecision
- public interactionType?: DocInteractionType
- public numberOfNavigations = 0
- public folderLevel: DocFolderLevel = 'ENTIRE_WORKSPACE'
-
- public docGenerationEventBase() {
- const undefinedProps = Object.entries(this)
- .filter(([key, value]) => value === undefined)
- .map(([key]) => key)
-
- if (undefinedProps.length > 0) {
- getLogger().debug(`DocV2GenerationEvent has undefined properties: ${undefinedProps.join(', ')}`)
- }
- const event: DocV2GenerationEvent = {
- conversationId: this.conversationId ?? '',
- numberOfGeneratedChars: this.numberOfGeneratedChars ?? 0,
- numberOfGeneratedLines: this.numberOfGeneratedLines ?? 0,
- numberOfGeneratedFiles: this.numberOfGeneratedFiles ?? 0,
- interactionType: this.interactionType,
- numberOfNavigations: this.numberOfNavigations,
- folderLevel: this.folderLevel,
- }
- return event
- }
-
- public docAcceptanceEventBase() {
- const undefinedProps = Object.entries(this)
- .filter(([key, value]) => value === undefined)
- .map(([key]) => key)
-
- if (undefinedProps.length > 0) {
- getLogger().debug(`DocV2AcceptanceEvent has undefined properties: ${undefinedProps.join(', ')}`)
- }
- const event: DocV2AcceptanceEvent = {
- conversationId: this.conversationId ?? '',
- numberOfAddedChars: this.numberOfAddedChars ?? 0,
- numberOfAddedLines: this.numberOfAddedLines ?? 0,
- numberOfAddedFiles: this.numberOfAddedFiles ?? 0,
- userDecision: this.userDecision ?? 'ACCEPTED',
- interactionType: this.interactionType ?? 'GENERATE_README',
- numberOfNavigations: this.numberOfNavigations ?? 0,
- folderLevel: this.folderLevel,
- }
- return event
- }
-
- public reset() {
- this.conversationId = undefined
- this.numberOfAddedChars = undefined
- this.numberOfAddedLines = undefined
- this.numberOfAddedFiles = undefined
- this.numberOfGeneratedChars = undefined
- this.numberOfGeneratedLines = undefined
- this.numberOfGeneratedFiles = undefined
- this.userDecision = undefined
- this.interactionType = undefined
- this.numberOfNavigations = 0
- this.folderLevel = 'ENTIRE_WORKSPACE'
- }
-}
diff --git a/packages/core/src/amazonqDoc/errors.ts b/packages/core/src/amazonqDoc/errors.ts
deleted file mode 100644
index fb1ffa033c2..00000000000
--- a/packages/core/src/amazonqDoc/errors.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ClientError, ContentLengthError as CommonContentLengthError } from '../shared/errors'
-import { i18n } from '../shared/i18n-helper'
-
-export class DocClientError extends ClientError {
- remainingIterations?: number
- constructor(message: string, code: string, remainingIterations?: number) {
- super(message, { code })
- this.remainingIterations = remainingIterations
- }
-}
-
-export class ReadmeTooLargeError extends DocClientError {
- constructor() {
- super(i18n('AWS.amazonq.doc.error.readmeTooLarge'), ReadmeTooLargeError.name)
- }
-}
-
-export class ReadmeUpdateTooLargeError extends DocClientError {
- constructor(remainingIterations: number) {
- super(i18n('AWS.amazonq.doc.error.readmeUpdateTooLarge'), ReadmeUpdateTooLargeError.name, remainingIterations)
- }
-}
-
-export class WorkspaceEmptyError extends DocClientError {
- constructor() {
- super(i18n('AWS.amazonq.doc.error.workspaceEmpty'), WorkspaceEmptyError.name)
- }
-}
-
-export class NoChangeRequiredException extends DocClientError {
- constructor() {
- super(i18n('AWS.amazonq.doc.error.noChangeRequiredException'), NoChangeRequiredException.name)
- }
-}
-
-export class PromptRefusalException extends DocClientError {
- constructor(remainingIterations: number) {
- super(i18n('AWS.amazonq.doc.error.promptRefusal'), PromptRefusalException.name, remainingIterations)
- }
-}
-
-export class ContentLengthError extends CommonContentLengthError {
- constructor() {
- super(i18n('AWS.amazonq.doc.error.contentLengthError'), { code: ContentLengthError.name })
- }
-}
-
-export class PromptTooVagueError extends DocClientError {
- constructor(remainingIterations: number) {
- super(i18n('AWS.amazonq.doc.error.promptTooVague'), PromptTooVagueError.name, remainingIterations)
- }
-}
-
-export class PromptUnrelatedError extends DocClientError {
- constructor(remainingIterations: number) {
- super(i18n('AWS.amazonq.doc.error.promptUnrelated'), PromptUnrelatedError.name, remainingIterations)
- }
-}
diff --git a/packages/core/src/amazonqDoc/index.ts b/packages/core/src/amazonqDoc/index.ts
deleted file mode 100644
index 7ba22e4b351..00000000000
--- a/packages/core/src/amazonqDoc/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export * from './types'
-export * from './session/sessionState'
-export * from './constants'
-export { Session } from './session/session'
-export { ChatControllerEventEmitters, DocController } from './controllers/chat/controller'
diff --git a/packages/core/src/amazonqDoc/messenger.ts b/packages/core/src/amazonqDoc/messenger.ts
deleted file mode 100644
index 3c6abfdf15f..00000000000
--- a/packages/core/src/amazonqDoc/messenger.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import { Messenger } from '../amazonq/commons/connector/baseMessenger'
-import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages'
-import { messageWithConversationId } from '../amazonqFeatureDev/userFacingText'
-import { i18n } from '../shared/i18n-helper'
-import { docGenerationProgressMessage, DocGenerationStep, Mode, NewSessionFollowUps } from './constants'
-import { inProgress } from './types'
-
-export class DocMessenger extends Messenger {
- public constructor(dispatcher: AppToWebViewMessageDispatcher, sender: string) {
- super(dispatcher, sender)
- }
-
- /** Sends a message in the chat and displays a prompt input progress bar to communicate the doc generation progress.
- * The text in the progress bar matches the current step shown in the message.
- *
- */
- public sendDocProgress(tabID: string, step: DocGenerationStep, progress: number, mode: Mode) {
- // Hide prompt input progress bar once all steps are completed
- if (step > DocGenerationStep.GENERATING_ARTIFACTS) {
- // eslint-disable-next-line unicorn/no-null
- this.sendUpdatePromptProgress(tabID, null)
- } else {
- const progressText =
- step === DocGenerationStep.UPLOAD_TO_S3
- ? `${i18n('AWS.amazonq.doc.answer.scanning')}...`
- : step === DocGenerationStep.SUMMARIZING_FILES
- ? `${i18n('AWS.amazonq.doc.answer.summarizing')}...`
- : `${i18n('AWS.amazonq.doc.answer.generating')}...`
- this.sendUpdatePromptProgress(tabID, inProgress(progress, progressText))
- }
-
- // The first step is answer-stream type, subequent updates are answer-part
- this.sendAnswer({
- type: step === DocGenerationStep.UPLOAD_TO_S3 ? 'answer-stream' : 'answer-part',
- tabID: tabID,
- disableChatInput: true,
- message: docGenerationProgressMessage(step, mode),
- })
- }
-
- public override sendErrorMessage(
- errorMessage: string,
- tabID: string,
- _retries: number,
- conversationId?: string,
- _showDefaultMessage?: boolean,
- enableUserInput?: boolean
- ) {
- if (enableUserInput) {
- this.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.doc.placeholder.editReadme'))
- this.sendChatInputEnabled(tabID, true)
- }
- this.sendAnswer({
- type: 'answer',
- tabID: tabID,
- message: errorMessage + messageWithConversationId(conversationId),
- followUps: enableUserInput ? [] : NewSessionFollowUps,
- disableChatInput: !enableUserInput,
- })
- }
-}
diff --git a/packages/core/src/amazonqDoc/session/session.ts b/packages/core/src/amazonqDoc/session/session.ts
deleted file mode 100644
index e3eb29d6d32..00000000000
--- a/packages/core/src/amazonqDoc/session/session.ts
+++ /dev/null
@@ -1,371 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { docScheme, featureName, Mode } from '../constants'
-import { DeletedFileInfo, Interaction, NewFileInfo, SessionState, SessionStateConfig } from '../types'
-import { DocPrepareCodeGenState } from './sessionState'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { AuthUtil } from '../../codewhisperer/util/authUtil'
-import { SessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import path from 'path'
-import { FeatureDevClient } from '../../amazonqFeatureDev/client/featureDev'
-import { TelemetryHelper } from '../../amazonq/util/telemetryHelper'
-import { ConversationNotStartedState } from '../../amazonqFeatureDev/session/sessionState'
-import { logWithConversationId } from '../../amazonqFeatureDev/userFacingText'
-import { ConversationIdNotFoundError, IllegalStateError } from '../../amazonqFeatureDev/errors'
-import { referenceLogText } from '../../amazonq/commons/model'
-import {
- DocInteractionType,
- DocV2AcceptanceEvent,
- DocV2GenerationEvent,
- SendTelemetryEventRequest,
- MetricData,
-} from '../../codewhisperer/client/codewhispereruserclient'
-import { getClientId, getOperatingSystem, getOptOutPreference } from '../../shared/telemetry/util'
-import { DocMessenger } from '../messenger'
-import { computeDiff } from '../../amazonq/commons/diff'
-import { ReferenceLogViewProvider } from '../../codewhisperer/service/referenceLogViewProvider'
-import fs from '../../shared/fs/fs'
-import globals from '../../shared/extensionGlobals'
-import { extensionVersion } from '../../shared/vscode/env'
-import { getLogger } from '../../shared/logger/logger'
-import { ContentLengthError as CommonContentLengthError } from '../../shared/errors'
-import { ContentLengthError } from '../errors'
-
-export class Session {
- private _state?: SessionState | Omit
- private task: string = ''
- private proxyClient: FeatureDevClient
- private _conversationId?: string
- private preloaderFinished = false
- private _latestMessage: string = ''
- private _telemetry: TelemetryHelper
-
- // Used to keep track of whether or not the current session is currently authenticating/needs authenticating
- public isAuthenticating: boolean
- private _reportedDocChanges: { [key: string]: string } = {}
-
- constructor(
- public readonly config: SessionConfig,
- private messenger: DocMessenger,
- public readonly tabID: string,
- initialState: Omit = new ConversationNotStartedState(tabID),
- proxyClient: FeatureDevClient = new FeatureDevClient()
- ) {
- this._state = initialState
- this.proxyClient = proxyClient
-
- this._telemetry = new TelemetryHelper()
- this.isAuthenticating = false
- }
-
- /**
- * Preload any events that have to run before a chat message can be sent
- */
- async preloader(msg: string) {
- if (!this.preloaderFinished) {
- await this.setupConversation(msg)
- this.preloaderFinished = true
- }
- }
-
- get state() {
- if (!this._state) {
- throw new IllegalStateError("State should be initialized before it's read")
- }
- return this._state
- }
-
- /**
- * setupConversation
- *
- * Starts a conversation with the backend and uploads the repo for the LLMs to be able to use it.
- */
- private async setupConversation(msg: string) {
- // Store the initial message when setting up the conversation so that if it fails we can retry with this message
- this._latestMessage = msg
-
- await telemetry.amazonq_startConversationInvoke.run(async (span) => {
- this._conversationId = await this.proxyClient.createConversation()
- getLogger().info(logWithConversationId(this.conversationId))
-
- span.record({ amazonqConversationId: this._conversationId, credentialStartUrl: AuthUtil.instance.startUrl })
- })
-
- this._state = new DocPrepareCodeGenState(
- {
- ...this.getSessionStateConfig(),
- conversationId: this.conversationId,
- uploadId: '',
- currentCodeGenerationId: undefined,
- },
- [],
- [],
- [],
- this.tabID,
- 0
- )
- }
-
- private getSessionStateConfig(): Omit {
- return {
- workspaceRoots: this.config.workspaceRoots,
- workspaceFolders: this.config.workspaceFolders,
- proxyClient: this.proxyClient,
- conversationId: this.conversationId,
- }
- }
-
- async send(msg: string, mode: Mode, folderPath?: string): Promise {
- // When the task/"thing to do" hasn't been set yet, we want it to be the incoming message
- if (this.task === '' && msg) {
- this.task = msg
- }
-
- this._latestMessage = msg
-
- return this.nextInteraction(msg, mode, folderPath)
- }
- private async nextInteraction(msg: string, mode: Mode, folderPath?: string) {
- try {
- const resp = await this.state.interact({
- task: this.task,
- msg,
- fs: this.config.fs,
- mode: mode,
- folderPath: folderPath,
- messenger: this.messenger,
- telemetry: this.telemetry,
- tokenSource: this.state.tokenSource,
- uploadHistory: this.state.uploadHistory,
- })
-
- if (resp.nextState) {
- if (!this.state?.tokenSource?.token.isCancellationRequested) {
- this.state?.tokenSource?.cancel()
- }
-
- // Move to the next state
- this._state = resp.nextState
- }
-
- return resp.interaction
- } catch (e) {
- if (e instanceof CommonContentLengthError) {
- getLogger().debug(`Content length validation failed: ${e.message}`)
- throw new ContentLengthError()
- }
- throw e
- }
- }
-
- public async updateFilesPaths(
- tabID: string,
- filePaths: NewFileInfo[],
- deletedFiles: DeletedFileInfo[],
- messageId: string,
- disableFileActions: boolean
- ) {
- this.messenger.updateFileComponent(tabID, filePaths, deletedFiles, messageId, disableFileActions)
- }
-
- public async insertChanges() {
- for (const filePath of this.state.filePaths?.filter((i) => !i.rejected) ?? []) {
- const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
-
- const uri = filePath.virtualMemoryUri
- const content = await this.config.fs.readFile(uri)
- const decodedContent = new TextDecoder().decode(content)
-
- await fs.mkdir(path.dirname(absolutePath))
- await fs.writeFile(absolutePath, decodedContent)
- }
-
- for (const filePath of this.state.deletedFiles?.filter((i) => !i.rejected) ?? []) {
- const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
- await fs.delete(absolutePath)
- }
-
- for (const ref of this.state.references ?? []) {
- ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref))
- }
- }
-
- private getFromReportedChanges(filepath: NewFileInfo) {
- const key = `${filepath.workspaceFolder.uri.fsPath}/${filepath.relativePath}`
- return this._reportedDocChanges[key]
- }
-
- private addToReportedChanges(filepath: NewFileInfo) {
- const key = `${filepath.workspaceFolder.uri.fsPath}/${filepath.relativePath}`
- this._reportedDocChanges[key] = filepath.fileContent
- }
-
- public async countGeneratedContent(interactionType?: DocInteractionType) {
- let totalGeneratedChars = 0
- let totalGeneratedLines = 0
- let totalGeneratedFiles = 0
- const filePaths = this.state.filePaths ?? []
-
- for (const filePath of filePaths) {
- const reportedDocChange = this.getFromReportedChanges(filePath)
- if (interactionType === 'GENERATE_README') {
- if (reportedDocChange) {
- const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath, reportedDocChange)
- totalGeneratedChars += charsAdded
- totalGeneratedLines += linesAdded
- } else {
- // If no changes are reported, this is the initial README generation and no comparison with existing files is needed
- const fileContent = filePath.fileContent
- totalGeneratedChars += fileContent.length
- totalGeneratedLines += fileContent.split('\n').length
- }
- } else {
- const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath, reportedDocChange)
- totalGeneratedChars += charsAdded
- totalGeneratedLines += linesAdded
- }
- this.addToReportedChanges(filePath)
- totalGeneratedFiles += 1
- }
- return {
- totalGeneratedChars,
- totalGeneratedLines,
- totalGeneratedFiles,
- }
- }
-
- public async countAddedContent(interactionType?: DocInteractionType) {
- let totalAddedChars = 0
- let totalAddedLines = 0
- let totalAddedFiles = 0
- const newFilePaths =
- this.state.filePaths?.filter((filePath) => !filePath.rejected && !filePath.changeApplied) ?? []
-
- for (const filePath of newFilePaths) {
- if (interactionType === 'GENERATE_README') {
- const fileContent = filePath.fileContent
- totalAddedChars += fileContent.length
- totalAddedLines += fileContent.split('\n').length
- } else {
- const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
- totalAddedChars += charsAdded
- totalAddedLines += linesAdded
- }
- totalAddedFiles += 1
- }
- return {
- totalAddedChars,
- totalAddedLines,
- totalAddedFiles,
- }
- }
-
- public async computeFilePathDiff(filePath: NewFileInfo, reportedChanges?: string) {
- const leftPath = `${filePath.workspaceFolder.uri.fsPath}/${filePath.relativePath}`
- const rightPath = filePath.virtualMemoryUri.path
- const diff = await computeDiff(leftPath, rightPath, this.tabID, docScheme, reportedChanges)
- return { leftPath, rightPath, ...diff }
- }
-
- public async sendDocMetricData(operationName: string, result: string) {
- const metricData = {
- metricName: 'Operation',
- metricValue: 1,
- timestamp: new Date(),
- product: 'DocGeneration',
- dimensions: [
- {
- name: 'operationName',
- value: operationName,
- },
- {
- name: 'result',
- value: result,
- },
- ],
- }
- await this.sendDocTelemetryEvent(metricData, 'metric')
- }
-
- public async sendDocTelemetryEvent(
- telemetryEvent: DocV2GenerationEvent | DocV2AcceptanceEvent | MetricData,
- eventType: 'generation' | 'acceptance' | 'metric'
- ) {
- const client = await this.proxyClient.getClient()
- const telemetryEventKey = {
- generation: 'docV2GenerationEvent',
- acceptance: 'docV2AcceptanceEvent',
- metric: 'metricData',
- }[eventType]
- try {
- const params: SendTelemetryEventRequest = {
- telemetryEvent: {
- [telemetryEventKey]: telemetryEvent,
- },
- optOutPreference: getOptOutPreference(),
- userContext: {
- ideCategory: 'VSCODE',
- operatingSystem: getOperatingSystem(),
- product: 'DocGeneration', // Should be the same as in JetBrains
- clientId: getClientId(globals.globalState),
- ideVersion: extensionVersion,
- },
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- }
-
- const response = await client.sendTelemetryEvent(params).promise()
- if (eventType === 'metric') {
- getLogger().debug(
- `${featureName}: successfully sent metricData: RequestId: ${response.$response.requestId}`
- )
- } else {
- getLogger().debug(
- `${featureName}: successfully sent docV2${eventType === 'generation' ? 'GenerationEvent' : 'AcceptanceEvent'}: ` +
- `ConversationId: ${(telemetryEvent as DocV2GenerationEvent | DocV2AcceptanceEvent).conversationId} ` +
- `RequestId: ${response.$response.requestId}`
- )
- }
- } catch (e) {
- const error = e as Error
- const eventTypeString = eventType === 'metric' ? 'metricData' : `doc ${eventType}`
- getLogger().error(
- `${featureName}: failed to send ${eventTypeString} telemetry: ${error.name}: ${error.message} ` +
- `RequestId: ${(e as any).$response?.requestId}`
- )
- }
- }
-
- get currentCodeGenerationId() {
- return this.state.currentCodeGenerationId
- }
-
- get uploadId() {
- if (!('uploadId' in this.state)) {
- throw new IllegalStateError("UploadId has to be initialized before it's read")
- }
- return this.state.uploadId
- }
-
- get conversationId() {
- if (!this._conversationId) {
- throw new ConversationIdNotFoundError()
- }
- return this._conversationId
- }
-
- // Used for cases where it is not needed to have conversationId
- get conversationIdUnsafe() {
- return this._conversationId
- }
-
- get latestMessage() {
- return this._latestMessage
- }
-
- get telemetry() {
- return this._telemetry
- }
-}
diff --git a/packages/core/src/amazonqDoc/session/sessionState.ts b/packages/core/src/amazonqDoc/session/sessionState.ts
deleted file mode 100644
index e95bc48f772..00000000000
--- a/packages/core/src/amazonqDoc/session/sessionState.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { DocGenerationStep, docScheme, getFileSummaryPercentage, Mode } from '../constants'
-
-import { i18n } from '../../shared/i18n-helper'
-
-import { CurrentWsFolders, NewFileInfo, SessionState, SessionStateAction, SessionStateConfig } from '../types'
-import {
- ContentLengthError,
- NoChangeRequiredException,
- PromptRefusalException,
- PromptTooVagueError,
- PromptUnrelatedError,
- ReadmeTooLargeError,
- ReadmeUpdateTooLargeError,
- WorkspaceEmptyError,
-} from '../errors'
-import { ApiClientError, ApiServiceError } from '../../amazonqFeatureDev/errors'
-import { DocMessenger } from '../messenger'
-import { BaseCodeGenState, BasePrepareCodeGenState, CreateNextStateParams } from '../../amazonq/session/sessionState'
-import { Intent } from '../../amazonq/commons/types'
-import { AmazonqCreateUpload, Span } from '../../shared/telemetry/telemetry'
-import { prepareRepoData, PrepareRepoDataOptions } from '../../amazonq/util/files'
-import { LlmError } from '../../amazonq/errors'
-
-export class DocCodeGenState extends BaseCodeGenState {
- protected handleProgress(messenger: DocMessenger, action: SessionStateAction, detail?: string): void {
- if (detail) {
- const progress = getFileSummaryPercentage(detail)
- messenger.sendDocProgress(
- this.tabID,
- progress === 100 ? DocGenerationStep.GENERATING_ARTIFACTS : DocGenerationStep.SUMMARIZING_FILES,
- progress,
- action.mode
- )
- }
- }
-
- protected getScheme(): string {
- return docScheme
- }
-
- protected getTimeoutErrorCode(): string {
- return 'DocGenerationTimeout'
- }
-
- protected handleGenerationComplete(
- messenger: DocMessenger,
- newFileInfo: NewFileInfo[],
- action: SessionStateAction
- ): void {
- messenger.sendDocProgress(this.tabID, DocGenerationStep.GENERATING_ARTIFACTS + 1, 100, action.mode)
- }
-
- protected handleError(messenger: DocMessenger, codegenResult: any): Error {
- // eslint-disable-next-line unicorn/no-null
- messenger.sendUpdatePromptProgress(this.tabID, null)
-
- switch (true) {
- case codegenResult.codeGenerationStatusDetail?.includes('README_TOO_LARGE'): {
- return new ReadmeTooLargeError()
- }
- case codegenResult.codeGenerationStatusDetail?.includes('README_UPDATE_TOO_LARGE'): {
- return new ReadmeUpdateTooLargeError(codegenResult.codeGenerationRemainingIterationCount || 0)
- }
- case codegenResult.codeGenerationStatusDetail?.includes('WORKSPACE_TOO_LARGE'): {
- return new ContentLengthError()
- }
- case codegenResult.codeGenerationStatusDetail?.includes('WORKSPACE_EMPTY'): {
- return new WorkspaceEmptyError()
- }
- case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_UNRELATED'): {
- return new PromptUnrelatedError(codegenResult.codeGenerationRemainingIterationCount || 0)
- }
- case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_TOO_VAGUE'): {
- return new PromptTooVagueError(codegenResult.codeGenerationRemainingIterationCount || 0)
- }
- case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_REFUSAL'): {
- return new PromptRefusalException(codegenResult.codeGenerationRemainingIterationCount || 0)
- }
- case codegenResult.codeGenerationStatusDetail?.includes('Guardrails'): {
- return new ApiClientError(
- i18n('AWS.amazonq.doc.error.docGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'GuardrailsException',
- 400
- )
- }
- case codegenResult.codeGenerationStatusDetail?.includes('EmptyPatch'): {
- if (codegenResult.codeGenerationStatusDetail?.includes('NO_CHANGE_REQUIRED')) {
- return new NoChangeRequiredException()
- }
-
- return new LlmError(i18n('AWS.amazonq.doc.error.docGen.default'), {
- code: 'EmptyPatchException',
- })
- }
- case codegenResult.codeGenerationStatusDetail?.includes('Throttling'): {
- return new ApiClientError(
- i18n('AWS.amazonq.featureDev.error.throttling'),
- 'GetTaskAssistCodeGeneration',
- 'ThrottlingException',
- 429
- )
- }
- default: {
- return new ApiServiceError(
- i18n('AWS.amazonq.doc.error.docGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'UnknownException',
- 500
- )
- }
- }
- }
-
- protected async startCodeGeneration(action: SessionStateAction, codeGenerationId: string): Promise {
- if (!action.tokenSource?.token.isCancellationRequested) {
- action.messenger.sendDocProgress(this.tabID, DocGenerationStep.SUMMARIZING_FILES, 0, action.mode as Mode)
- }
-
- await this.config.proxyClient.startCodeGeneration(
- this.config.conversationId,
- this.config.uploadId,
- action.msg,
- Intent.DOC,
- codeGenerationId,
- undefined,
- action.folderPath ? { documentation: { type: 'README', scope: action.folderPath } } : undefined
- )
- }
-
- protected override createNextState(config: SessionStateConfig, params: CreateNextStateParams): SessionState {
- return super.createNextState(config, params, DocPrepareCodeGenState)
- }
-}
-
-export class DocPrepareCodeGenState extends BasePrepareCodeGenState {
- protected preUpload(action: SessionStateAction): void {
- // Do nothing
- }
-
- protected postUpload(action: SessionStateAction): void {
- // Do nothing
- }
-
- protected override createNextState(config: SessionStateConfig): SessionState {
- return super.createNextState(config, DocCodeGenState)
- }
-
- protected override async prepareProjectZip(
- workspaceRoots: string[],
- workspaceFolders: CurrentWsFolders,
- span: Span,
- options: PrepareRepoDataOptions
- ) {
- return await prepareRepoData(workspaceRoots, workspaceFolders, span, {
- ...options,
- isIncludeInfraDiagram: true,
- })
- }
-}
diff --git a/packages/core/src/amazonqDoc/storages/chatSession.ts b/packages/core/src/amazonqDoc/storages/chatSession.ts
deleted file mode 100644
index 34fb9f5404e..00000000000
--- a/packages/core/src/amazonqDoc/storages/chatSession.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { BaseChatSessionStorage } from '../../amazonq/commons/baseChatStorage'
-import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import { docScheme } from '../constants'
-import { DocMessenger } from '../messenger'
-import { Session } from '../session/session'
-
-export class DocChatSessionStorage extends BaseChatSessionStorage {
- constructor(protected readonly messenger: DocMessenger) {
- super()
- }
-
- override async createSession(tabID: string): Promise {
- const sessionConfig = await createSessionConfig(docScheme)
- const session = new Session(sessionConfig, this.messenger, tabID)
- this.sessions.set(tabID, session)
- return session
- }
-}
diff --git a/packages/core/src/amazonqDoc/types.ts b/packages/core/src/amazonqDoc/types.ts
deleted file mode 100644
index 005353d0e23..00000000000
--- a/packages/core/src/amazonqDoc/types.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatItemButton, MynahIcons, ProgressField } from '@aws/mynah-ui'
-import {
- LLMResponseType,
- SessionStorage,
- SessionInfo,
- DeletedFileInfo,
- NewFileInfo,
- NewFileZipContents,
- SessionStateConfig,
- SessionStatePhase,
- DevPhase,
- Interaction,
- CurrentWsFolders,
- CodeGenerationStatus,
- SessionState as FeatureDevSessionState,
- SessionStateAction as FeatureDevSessionStateAction,
- SessionStateInteraction as FeatureDevSessionStateInteraction,
-} from '../amazonq/commons/types'
-
-import { Mode } from './constants'
-import { DocMessenger } from './messenger'
-
-export const cancelDocGenButton: ChatItemButton = {
- id: 'cancel-doc-generation',
- text: 'Cancel',
- icon: 'cancel' as MynahIcons,
-}
-
-export const inProgress = (progress: number, text: string): ProgressField => {
- return {
- status: 'default',
- text,
- value: progress === 100 ? -1 : progress,
- actions: [cancelDocGenButton],
- }
-}
-
-export interface SessionStateInteraction extends FeatureDevSessionStateInteraction {
- nextState: SessionState | Omit | undefined
- interaction: Interaction
-}
-
-export interface SessionState extends FeatureDevSessionState {
- interact(action: SessionStateAction): Promise
-}
-
-export interface SessionStateAction extends FeatureDevSessionStateAction {
- messenger: DocMessenger
- mode: Mode
- folderPath?: string
-}
-
-export enum MetricDataOperationName {
- StartDocGeneration = 'StartDocGeneration',
- EndDocGeneration = 'EndDocGeneration',
-}
-
-export enum MetricDataResult {
- Success = 'Success',
- Fault = 'Fault',
- Error = 'Error',
- LlmFailure = 'LLMFailure',
-}
-
-export {
- LLMResponseType,
- SessionStorage,
- SessionInfo,
- DeletedFileInfo,
- NewFileInfo,
- NewFileZipContents,
- SessionStateConfig,
- SessionStatePhase,
- DevPhase,
- Interaction,
- CodeGenerationStatus,
- CurrentWsFolders,
-}
diff --git a/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts b/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts
deleted file mode 100644
index c6960b15fcc..00000000000
--- a/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatControllerEventEmitters } from '../../controllers/chat/controller'
-import { MessageListener } from '../../../amazonq/messages/messageListener'
-import { ExtensionMessage } from '../../../amazonq/webview/ui/commands'
-
-export interface UIMessageListenerProps {
- readonly chatControllerEventEmitters: ChatControllerEventEmitters
- readonly webViewMessageListener: MessageListener
-}
-
-export class UIMessageListener {
- private docGenerationControllerEventsEmitters: ChatControllerEventEmitters | undefined
- private webViewMessageListener: MessageListener
-
- constructor(props: UIMessageListenerProps) {
- this.docGenerationControllerEventsEmitters = props.chatControllerEventEmitters
- this.webViewMessageListener = props.webViewMessageListener
-
- // Now we are listening to events that get sent from amazonq/webview/actions/actionListener (e.g. the tab)
- this.webViewMessageListener.onMessage((msg) => {
- this.handleMessage(msg)
- })
- }
-
- private handleMessage(msg: ExtensionMessage) {
- switch (msg.command) {
- case 'chat-prompt':
- this.processChatMessage(msg)
- break
- case 'follow-up-was-clicked':
- this.followUpClicked(msg)
- break
- case 'open-diff':
- this.openDiff(msg)
- break
- case 'chat-item-voted':
- this.chatItemVoted(msg)
- break
- case 'chat-item-feedback':
- this.chatItemFeedback(msg)
- break
- case 'stop-response':
- this.stopResponse(msg)
- break
- case 'new-tab-was-created':
- this.tabOpened(msg)
- break
- case 'tab-was-removed':
- this.tabClosed(msg)
- break
- case 'auth-follow-up-was-clicked':
- this.authClicked(msg)
- break
- case 'response-body-link-click':
- this.processResponseBodyLinkClick(msg)
- break
- case 'insert_code_at_cursor_position':
- this.insertCodeAtPosition(msg)
- break
- case 'file-click':
- this.fileClicked(msg)
- break
- case 'form-action-click':
- this.formActionClicked(msg)
- break
- }
- }
-
- private chatItemVoted(msg: any) {
- this.docGenerationControllerEventsEmitters?.processChatItemVotedMessage.fire({
- tabID: msg.tabID,
- command: msg.command,
- vote: msg.vote,
- messageId: msg.messageId,
- })
- }
-
- private chatItemFeedback(msg: any) {
- this.docGenerationControllerEventsEmitters?.processChatItemFeedbackMessage.fire(msg)
- }
-
- private processChatMessage(msg: any) {
- this.docGenerationControllerEventsEmitters?.processHumanChatMessage.fire({
- message: msg.chatMessage,
- tabID: msg.tabID,
- })
- }
-
- private followUpClicked(msg: any) {
- this.docGenerationControllerEventsEmitters?.followUpClicked.fire({
- followUp: msg.followUp,
- tabID: msg.tabID,
- })
- }
-
- private formActionClicked(msg: any) {
- this.docGenerationControllerEventsEmitters?.formActionClicked.fire({
- ...msg,
- })
- }
-
- private fileClicked(msg: any) {
- this.docGenerationControllerEventsEmitters?.fileClicked.fire({
- tabID: msg.tabID,
- filePath: msg.filePath,
- actionName: msg.actionName,
- messageId: msg.messageId,
- })
- }
-
- private openDiff(msg: any) {
- this.docGenerationControllerEventsEmitters?.openDiff.fire({
- tabID: msg.tabID,
- filePath: msg.filePath,
- deleted: msg.deleted,
- messageId: msg.messageId,
- })
- }
-
- private stopResponse(msg: any) {
- this.docGenerationControllerEventsEmitters?.stopResponse.fire({
- tabID: msg.tabID,
- })
- }
-
- private tabOpened(msg: any) {
- this.docGenerationControllerEventsEmitters?.tabOpened.fire({
- tabID: msg.tabID,
- })
- }
-
- private tabClosed(msg: any) {
- this.docGenerationControllerEventsEmitters?.tabClosed.fire({
- tabID: msg.tabID,
- })
- }
-
- private authClicked(msg: any) {
- this.docGenerationControllerEventsEmitters?.authClicked.fire({
- tabID: msg.tabID,
- authType: msg.authType,
- })
- }
-
- private processResponseBodyLinkClick(msg: any) {
- this.docGenerationControllerEventsEmitters?.processResponseBodyLinkClick.fire({
- command: msg.command,
- messageId: msg.messageId,
- tabID: msg.tabID,
- link: msg.link,
- })
- }
-
- private insertCodeAtPosition(msg: any) {
- this.docGenerationControllerEventsEmitters?.insertCodeAtPositionClicked.fire({
- command: msg.command,
- messageId: msg.messageId,
- tabID: msg.tabID,
- code: msg.code,
- insertionTargetType: msg.insertionTargetType,
- codeReference: msg.codeReference,
- })
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/app.ts b/packages/core/src/amazonqFeatureDev/app.ts
deleted file mode 100644
index fd0652fd0e4..00000000000
--- a/packages/core/src/amazonqFeatureDev/app.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { UIMessageListener } from './views/actions/uiMessageListener'
-import { ChatControllerEventEmitters, FeatureDevController } from './controllers/chat/controller'
-import { AmazonQAppInitContext } from '../amazonq/apps/initContext'
-import { MessageListener } from '../amazonq/messages/messageListener'
-import { fromQueryToParameters } from '../shared/utilities/uriUtils'
-import { getLogger } from '../shared/logger/logger'
-import { TabIdNotFoundError } from './errors'
-import { featureDevChat, featureDevScheme } from './constants'
-import globals from '../shared/extensionGlobals'
-import { FeatureDevChatSessionStorage } from './storages/chatSession'
-import { AuthUtil } from '../codewhisperer/util/authUtil'
-import { debounce } from 'lodash'
-import { Messenger } from '../amazonq/commons/connector/baseMessenger'
-import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages'
-
-export function init(appContext: AmazonQAppInitContext) {
- const featureDevChatControllerEventEmitters: ChatControllerEventEmitters = {
- processHumanChatMessage: new vscode.EventEmitter(),
- followUpClicked: new vscode.EventEmitter(),
- openDiff: new vscode.EventEmitter(),
- processChatItemVotedMessage: new vscode.EventEmitter(),
- processChatItemFeedbackMessage: new vscode.EventEmitter(),
- stopResponse: new vscode.EventEmitter(),
- tabOpened: new vscode.EventEmitter(),
- tabClosed: new vscode.EventEmitter(),
- authClicked: new vscode.EventEmitter(),
- processResponseBodyLinkClick: new vscode.EventEmitter(),
- insertCodeAtPositionClicked: new vscode.EventEmitter(),
- fileClicked: new vscode.EventEmitter(),
- storeCodeResultMessageId: new vscode.EventEmitter(),
- }
-
- const messenger = new Messenger(
- new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()),
- featureDevChat
- )
- const sessionStorage = new FeatureDevChatSessionStorage(messenger)
-
- new FeatureDevController(
- featureDevChatControllerEventEmitters,
- messenger,
- sessionStorage,
- appContext.onDidChangeAmazonQVisibility.event
- )
-
- const featureDevProvider = new (class implements vscode.TextDocumentContentProvider {
- async provideTextDocumentContent(uri: vscode.Uri): Promise {
- const params = fromQueryToParameters(uri.query)
-
- const tabID = params.get('tabID')
- if (!tabID) {
- getLogger().error(`Unable to find tabID from ${uri.toString()}`)
- throw new TabIdNotFoundError()
- }
-
- const session = await sessionStorage.getSession(tabID)
- const content = await session.config.fs.readFile(uri)
- const decodedContent = new TextDecoder().decode(content)
- return decodedContent
- }
- })()
-
- const textDocumentProvider = vscode.workspace.registerTextDocumentContentProvider(
- featureDevScheme,
- featureDevProvider
- )
-
- globals.context.subscriptions.push(textDocumentProvider)
-
- const featureDevChatUIInputEventEmitter = new vscode.EventEmitter()
-
- new UIMessageListener({
- chatControllerEventEmitters: featureDevChatControllerEventEmitters,
- webViewMessageListener: new MessageListener(featureDevChatUIInputEventEmitter),
- })
-
- const debouncedEvent = debounce(async () => {
- const authenticated = (await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'
- let authenticatingSessionIDs: string[] = []
- if (authenticated) {
- const authenticatingSessions = sessionStorage.getAuthenticatingSessions()
-
- authenticatingSessionIDs = authenticatingSessions.map((session) => session.tabID)
-
- // We've already authenticated these sessions
- for (const session of authenticatingSessions) {
- session.isAuthenticating = false
- }
- }
-
- messenger.sendAuthenticationUpdate(authenticated, authenticatingSessionIDs)
- }, 500)
-
- AuthUtil.instance.secondaryAuth.onDidChangeActiveConnection(() => {
- return debouncedEvent()
- })
- AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => {
- return debouncedEvent()
- })
-}
diff --git a/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json b/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json
deleted file mode 100644
index 812bbd4fd69..00000000000
--- a/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json
+++ /dev/null
@@ -1,5640 +0,0 @@
-{
- "version": "2.0",
- "metadata": {
- "apiVersion": "2022-11-11",
- "auth": ["smithy.api#httpBearerAuth"],
- "endpointPrefix": "amazoncodewhispererservice",
- "jsonVersion": "1.0",
- "protocol": "json",
- "protocols": ["json"],
- "serviceFullName": "Amazon CodeWhisperer",
- "serviceId": "CodeWhispererRuntime",
- "signatureVersion": "bearer",
- "signingName": "amazoncodewhispererservice",
- "targetPrefix": "AmazonCodeWhispererService",
- "uid": "codewhispererruntime-2022-11-11"
- },
- "operations": {
- "CreateArtifactUploadUrl": {
- "name": "CreateArtifactUploadUrl",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "CreateUploadUrlRequest"
- },
- "output": {
- "shape": "CreateUploadUrlResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "CreateTaskAssistConversation": {
- "name": "CreateTaskAssistConversation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "CreateTaskAssistConversationRequest"
- },
- "output": {
- "shape": "CreateTaskAssistConversationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ServiceQuotaExceededException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "CreateUploadUrl": {
- "name": "CreateUploadUrl",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "CreateUploadUrlRequest"
- },
- "output": {
- "shape": "CreateUploadUrlResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "ServiceQuotaExceededException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "CreateUserMemoryEntry": {
- "name": "CreateUserMemoryEntry",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "CreateUserMemoryEntryInput"
- },
- "output": {
- "shape": "CreateUserMemoryEntryOutput"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "CreateWorkspace": {
- "name": "CreateWorkspace",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "CreateWorkspaceRequest"
- },
- "output": {
- "shape": "CreateWorkspaceResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "DeleteTaskAssistConversation": {
- "name": "DeleteTaskAssistConversation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "DeleteTaskAssistConversationRequest"
- },
- "output": {
- "shape": "DeleteTaskAssistConversationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "DeleteUserMemoryEntry": {
- "name": "DeleteUserMemoryEntry",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "DeleteUserMemoryEntryInput"
- },
- "output": {
- "shape": "DeleteUserMemoryEntryOutput"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "DeleteWorkspace": {
- "name": "DeleteWorkspace",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "DeleteWorkspaceRequest"
- },
- "output": {
- "shape": "DeleteWorkspaceResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GenerateCompletions": {
- "name": "GenerateCompletions",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GenerateCompletionsRequest"
- },
- "output": {
- "shape": "GenerateCompletionsResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetCodeAnalysis": {
- "name": "GetCodeAnalysis",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetCodeAnalysisRequest"
- },
- "output": {
- "shape": "GetCodeAnalysisResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetCodeFixJob": {
- "name": "GetCodeFixJob",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetCodeFixJobRequest"
- },
- "output": {
- "shape": "GetCodeFixJobResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetTaskAssistCodeGeneration": {
- "name": "GetTaskAssistCodeGeneration",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetTaskAssistCodeGenerationRequest"
- },
- "output": {
- "shape": "GetTaskAssistCodeGenerationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetTestGeneration": {
- "name": "GetTestGeneration",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetTestGenerationRequest"
- },
- "output": {
- "shape": "GetTestGenerationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetTransformation": {
- "name": "GetTransformation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetTransformationRequest"
- },
- "output": {
- "shape": "GetTransformationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "GetTransformationPlan": {
- "name": "GetTransformationPlan",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "GetTransformationPlanRequest"
- },
- "output": {
- "shape": "GetTransformationPlanResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListAvailableCustomizations": {
- "name": "ListAvailableCustomizations",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListAvailableCustomizationsRequest"
- },
- "output": {
- "shape": "ListAvailableCustomizationsResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListAvailableProfiles": {
- "name": "ListAvailableProfiles",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListAvailableProfilesRequest"
- },
- "output": {
- "shape": "ListAvailableProfilesResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListCodeAnalysisFindings": {
- "name": "ListCodeAnalysisFindings",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListCodeAnalysisFindingsRequest"
- },
- "output": {
- "shape": "ListCodeAnalysisFindingsResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListEvents": {
- "name": "ListEvents",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListEventsRequest"
- },
- "output": {
- "shape": "ListEventsResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListFeatureEvaluations": {
- "name": "ListFeatureEvaluations",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListFeatureEvaluationsRequest"
- },
- "output": {
- "shape": "ListFeatureEvaluationsResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListUserMemoryEntries": {
- "name": "ListUserMemoryEntries",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListUserMemoryEntriesInput"
- },
- "output": {
- "shape": "ListUserMemoryEntriesOutput"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ListWorkspaceMetadata": {
- "name": "ListWorkspaceMetadata",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ListWorkspaceMetadataRequest"
- },
- "output": {
- "shape": "ListWorkspaceMetadataResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "ResumeTransformation": {
- "name": "ResumeTransformation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "ResumeTransformationRequest"
- },
- "output": {
- "shape": "ResumeTransformationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "SendTelemetryEvent": {
- "name": "SendTelemetryEvent",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "SendTelemetryEventRequest"
- },
- "output": {
- "shape": "SendTelemetryEventResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "StartCodeAnalysis": {
- "name": "StartCodeAnalysis",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StartCodeAnalysisRequest"
- },
- "output": {
- "shape": "StartCodeAnalysisResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "StartCodeFixJob": {
- "name": "StartCodeFixJob",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StartCodeFixJobRequest"
- },
- "output": {
- "shape": "StartCodeFixJobResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "StartTaskAssistCodeGeneration": {
- "name": "StartTaskAssistCodeGeneration",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StartTaskAssistCodeGenerationRequest"
- },
- "output": {
- "shape": "StartTaskAssistCodeGenerationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "ServiceQuotaExceededException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "StartTestGeneration": {
- "name": "StartTestGeneration",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StartTestGenerationRequest"
- },
- "output": {
- "shape": "StartTestGenerationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ],
- "idempotent": true
- },
- "StartTransformation": {
- "name": "StartTransformation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StartTransformationRequest"
- },
- "output": {
- "shape": "StartTransformationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ConflictException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- },
- "StopTransformation": {
- "name": "StopTransformation",
- "http": {
- "method": "POST",
- "requestUri": "/"
- },
- "input": {
- "shape": "StopTransformationRequest"
- },
- "output": {
- "shape": "StopTransformationResponse"
- },
- "errors": [
- {
- "shape": "ThrottlingException"
- },
- {
- "shape": "ResourceNotFoundException"
- },
- {
- "shape": "InternalServerException"
- },
- {
- "shape": "ValidationException"
- },
- {
- "shape": "AccessDeniedException"
- }
- ]
- }
- },
- "shapes": {
- "AccessDeniedException": {
- "type": "structure",
- "required": ["message"],
- "members": {
- "message": {
- "shape": "String"
- },
- "reason": {
- "shape": "AccessDeniedExceptionReason"
- }
- },
- "exception": true
- },
- "AccessDeniedExceptionReason": {
- "type": "string",
- "enum": ["UNAUTHORIZED_CUSTOMIZATION_RESOURCE_ACCESS"]
- },
- "ActiveFunctionalityList": {
- "type": "list",
- "member": {
- "shape": "FunctionalityName"
- },
- "max": 10,
- "min": 0
- },
- "AdditionalContentEntry": {
- "type": "structure",
- "required": ["name", "description"],
- "members": {
- "name": {
- "shape": "AdditionalContentEntryNameString"
- },
- "description": {
- "shape": "AdditionalContentEntryDescriptionString"
- },
- "innerContext": {
- "shape": "AdditionalContentEntryInnerContextString"
- }
- }
- },
- "AdditionalContentEntryDescriptionString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "AdditionalContentEntryInnerContextString": {
- "type": "string",
- "max": 8192,
- "min": 1,
- "sensitive": true
- },
- "AdditionalContentEntryNameString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "pattern": "[a-z]+(?:-[a-z0-9]+)*",
- "sensitive": true
- },
- "AdditionalContentList": {
- "type": "list",
- "member": {
- "shape": "AdditionalContentEntry"
- },
- "max": 20,
- "min": 0
- },
- "AppStudioState": {
- "type": "structure",
- "required": ["namespace", "propertyName", "propertyContext"],
- "members": {
- "namespace": {
- "shape": "AppStudioStateNamespaceString"
- },
- "propertyName": {
- "shape": "AppStudioStatePropertyNameString"
- },
- "propertyValue": {
- "shape": "AppStudioStatePropertyValueString"
- },
- "propertyContext": {
- "shape": "AppStudioStatePropertyContextString"
- }
- }
- },
- "AppStudioStateNamespaceString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "AppStudioStatePropertyContextString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "AppStudioStatePropertyNameString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "AppStudioStatePropertyValueString": {
- "type": "string",
- "max": 10240,
- "min": 0,
- "sensitive": true
- },
- "ApplicationProperties": {
- "type": "structure",
- "required": ["tenantId", "applicationArn", "tenantUrl", "applicationType"],
- "members": {
- "tenantId": {
- "shape": "TenantId"
- },
- "applicationArn": {
- "shape": "ResourceArn"
- },
- "tenantUrl": {
- "shape": "Url"
- },
- "applicationType": {
- "shape": "FunctionalityName"
- }
- }
- },
- "ApplicationPropertiesList": {
- "type": "list",
- "member": {
- "shape": "ApplicationProperties"
- }
- },
- "ArtifactId": {
- "type": "string",
- "max": 126,
- "min": 1,
- "pattern": "[a-zA-Z0-9-_]+"
- },
- "ArtifactMap": {
- "type": "map",
- "key": {
- "shape": "ArtifactType"
- },
- "value": {
- "shape": "UploadId"
- },
- "max": 64,
- "min": 1
- },
- "ArtifactType": {
- "type": "string",
- "enum": ["SourceCode", "BuiltJars"]
- },
- "AssistantResponseMessage": {
- "type": "structure",
- "required": ["content"],
- "members": {
- "messageId": {
- "shape": "MessageId"
- },
- "content": {
- "shape": "AssistantResponseMessageContentString"
- },
- "supplementaryWebLinks": {
- "shape": "SupplementaryWebLinks"
- },
- "references": {
- "shape": "References"
- },
- "followupPrompt": {
- "shape": "FollowupPrompt"
- },
- "toolUses": {
- "shape": "ToolUses"
- }
- }
- },
- "AssistantResponseMessageContentString": {
- "type": "string",
- "max": 100000,
- "min": 0,
- "sensitive": true
- },
- "AttributesMap": {
- "type": "map",
- "key": {
- "shape": "AttributesMapKeyString"
- },
- "value": {
- "shape": "StringList"
- }
- },
- "AttributesMapKeyString": {
- "type": "string",
- "max": 128,
- "min": 1
- },
- "Base64EncodedPaginationToken": {
- "type": "string",
- "max": 2048,
- "min": 1,
- "pattern": "(?:[A-Za-z0-9\\+/]{4})*(?:[A-Za-z0-9\\+/]{2}\\=\\=|[A-Za-z0-9\\+/]{3}\\=)?"
- },
- "Boolean": {
- "type": "boolean",
- "box": true
- },
- "ByUserAnalytics": {
- "type": "structure",
- "required": ["toggle"],
- "members": {
- "s3Uri": {
- "shape": "S3Uri"
- },
- "toggle": {
- "shape": "OptInFeatureToggle"
- }
- }
- },
- "ChatAddMessageEvent": {
- "type": "structure",
- "required": ["conversationId", "messageId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "messageId": {
- "shape": "MessageId"
- },
- "customizationArn": {
- "shape": "CustomizationArn"
- },
- "userIntent": {
- "shape": "UserIntent"
- },
- "hasCodeSnippet": {
- "shape": "Boolean"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "activeEditorTotalCharacters": {
- "shape": "Integer"
- },
- "timeToFirstChunkMilliseconds": {
- "shape": "Double"
- },
- "timeBetweenChunks": {
- "shape": "timeBetweenChunks"
- },
- "fullResponselatency": {
- "shape": "Double"
- },
- "requestLength": {
- "shape": "Integer"
- },
- "responseLength": {
- "shape": "Integer"
- },
- "numberOfCodeBlocks": {
- "shape": "Integer"
- },
- "hasProjectLevelContext": {
- "shape": "Boolean"
- }
- }
- },
- "ChatHistory": {
- "type": "list",
- "member": {
- "shape": "ChatMessage"
- },
- "max": 250,
- "min": 0
- },
- "ChatInteractWithMessageEvent": {
- "type": "structure",
- "required": ["conversationId", "messageId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "messageId": {
- "shape": "MessageId"
- },
- "customizationArn": {
- "shape": "CustomizationArn"
- },
- "interactionType": {
- "shape": "ChatMessageInteractionType"
- },
- "interactionTarget": {
- "shape": "ChatInteractWithMessageEventInteractionTargetString"
- },
- "acceptedCharacterCount": {
- "shape": "Integer"
- },
- "acceptedLineCount": {
- "shape": "Integer"
- },
- "acceptedSnippetHasReference": {
- "shape": "Boolean"
- },
- "hasProjectLevelContext": {
- "shape": "Boolean"
- },
- "userIntent": {
- "shape": "UserIntent"
- },
- "addedIdeDiagnostics": {
- "shape": "IdeDiagnosticList"
- },
- "removedIdeDiagnostics": {
- "shape": "IdeDiagnosticList"
- }
- }
- },
- "ChatInteractWithMessageEventInteractionTargetString": {
- "type": "string",
- "max": 1024,
- "min": 1
- },
- "ChatMessage": {
- "type": "structure",
- "members": {
- "userInputMessage": {
- "shape": "UserInputMessage"
- },
- "assistantResponseMessage": {
- "shape": "AssistantResponseMessage"
- }
- },
- "union": true
- },
- "ChatMessageInteractionType": {
- "type": "string",
- "enum": [
- "INSERT_AT_CURSOR",
- "COPY_SNIPPET",
- "COPY",
- "CLICK_LINK",
- "CLICK_BODY_LINK",
- "CLICK_FOLLOW_UP",
- "HOVER_REFERENCE",
- "UPVOTE",
- "DOWNVOTE"
- ]
- },
- "ChatTriggerType": {
- "type": "string",
- "enum": ["MANUAL", "DIAGNOSTIC", "INLINE_CHAT"]
- },
- "ChatUserModificationEvent": {
- "type": "structure",
- "required": ["conversationId", "messageId", "modificationPercentage"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "customizationArn": {
- "shape": "CustomizationArn"
- },
- "messageId": {
- "shape": "MessageId"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "modificationPercentage": {
- "shape": "Double"
- },
- "hasProjectLevelContext": {
- "shape": "Boolean"
- }
- }
- },
- "ClientId": {
- "type": "string",
- "max": 255,
- "min": 1
- },
- "CodeAnalysisFindingsSchema": {
- "type": "string",
- "enum": ["codeanalysis/findings/1.0"]
- },
- "CodeAnalysisScope": {
- "type": "string",
- "enum": ["FILE", "PROJECT"]
- },
- "CodeAnalysisStatus": {
- "type": "string",
- "enum": ["Completed", "Pending", "Failed"]
- },
- "CodeAnalysisUploadContext": {
- "type": "structure",
- "required": ["codeScanName"],
- "members": {
- "codeScanName": {
- "shape": "CodeScanName"
- }
- }
- },
- "CodeCoverageEvent": {
- "type": "structure",
- "required": ["programmingLanguage", "acceptedCharacterCount", "totalCharacterCount", "timestamp"],
- "members": {
- "customizationArn": {
- "shape": "CustomizationArn"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "acceptedCharacterCount": {
- "shape": "PrimitiveInteger"
- },
- "totalCharacterCount": {
- "shape": "PrimitiveInteger"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "unmodifiedAcceptedCharacterCount": {
- "shape": "PrimitiveInteger"
- },
- "totalNewCodeCharacterCount": {
- "shape": "PrimitiveInteger"
- },
- "totalNewCodeLineCount": {
- "shape": "PrimitiveInteger"
- },
- "userWrittenCodeCharacterCount": {
- "shape": "CodeCoverageEventUserWrittenCodeCharacterCountInteger"
- },
- "userWrittenCodeLineCount": {
- "shape": "CodeCoverageEventUserWrittenCodeLineCountInteger"
- }
- }
- },
- "CodeCoverageEventUserWrittenCodeCharacterCountInteger": {
- "type": "integer",
- "min": 0
- },
- "CodeCoverageEventUserWrittenCodeLineCountInteger": {
- "type": "integer",
- "min": 0
- },
- "CodeDescription": {
- "type": "structure",
- "required": ["href"],
- "members": {
- "href": {
- "shape": "CodeDescriptionHrefString"
- }
- }
- },
- "CodeDescriptionHrefString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "CodeFixAcceptanceEvent": {
- "type": "structure",
- "required": ["jobId"],
- "members": {
- "jobId": {
- "shape": "String"
- },
- "ruleId": {
- "shape": "String"
- },
- "detectorId": {
- "shape": "String"
- },
- "findingId": {
- "shape": "String"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "linesOfCodeAccepted": {
- "shape": "Integer"
- },
- "charsOfCodeAccepted": {
- "shape": "Integer"
- }
- }
- },
- "CodeFixGenerationEvent": {
- "type": "structure",
- "required": ["jobId"],
- "members": {
- "jobId": {
- "shape": "String"
- },
- "ruleId": {
- "shape": "String"
- },
- "detectorId": {
- "shape": "String"
- },
- "findingId": {
- "shape": "String"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "linesOfCodeGenerated": {
- "shape": "Integer"
- },
- "charsOfCodeGenerated": {
- "shape": "Integer"
- }
- }
- },
- "CodeFixJobStatus": {
- "type": "string",
- "enum": ["Succeeded", "InProgress", "Failed"]
- },
- "CodeFixName": {
- "type": "string",
- "max": 128,
- "min": 1,
- "pattern": "[a-zA-Z0-9-_$:.]*"
- },
- "CodeFixUploadContext": {
- "type": "structure",
- "required": ["codeFixName"],
- "members": {
- "codeFixName": {
- "shape": "CodeFixName"
- }
- }
- },
- "CodeGenerationId": {
- "type": "string",
- "max": 128,
- "min": 1
- },
- "CodeGenerationStatus": {
- "type": "structure",
- "required": ["status", "currentStage"],
- "members": {
- "status": {
- "shape": "CodeGenerationWorkflowStatus"
- },
- "currentStage": {
- "shape": "CodeGenerationWorkflowStage"
- }
- }
- },
- "CodeGenerationStatusDetail": {
- "type": "string",
- "sensitive": true
- },
- "CodeGenerationWorkflowStage": {
- "type": "string",
- "enum": ["InitialCodeGeneration", "CodeRefinement"]
- },
- "CodeGenerationWorkflowStatus": {
- "type": "string",
- "enum": ["InProgress", "Complete", "Failed"]
- },
- "CodeScanEvent": {
- "type": "structure",
- "required": ["programmingLanguage", "codeScanJobId", "timestamp"],
- "members": {
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "codeScanJobId": {
- "shape": "CodeScanJobId"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "codeAnalysisScope": {
- "shape": "CodeAnalysisScope"
- }
- }
- },
- "CodeScanFailedEvent": {
- "type": "structure",
- "required": ["programmingLanguage", "codeScanJobId", "timestamp"],
- "members": {
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "codeScanJobId": {
- "shape": "CodeScanJobId"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "codeAnalysisScope": {
- "shape": "CodeAnalysisScope"
- }
- }
- },
- "CodeScanJobId": {
- "type": "string",
- "max": 128,
- "min": 1
- },
- "CodeScanName": {
- "type": "string",
- "max": 128,
- "min": 1
- },
- "CodeScanRemediationsEvent": {
- "type": "structure",
- "members": {
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "CodeScanRemediationsEventType": {
- "shape": "CodeScanRemediationsEventType"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "detectorId": {
- "shape": "String"
- },
- "findingId": {
- "shape": "String"
- },
- "ruleId": {
- "shape": "String"
- },
- "component": {
- "shape": "String"
- },
- "reason": {
- "shape": "String"
- },
- "result": {
- "shape": "String"
- },
- "includesFix": {
- "shape": "Boolean"
- }
- }
- },
- "CodeScanRemediationsEventType": {
- "type": "string",
- "enum": ["CODESCAN_ISSUE_HOVER", "CODESCAN_ISSUE_APPLY_FIX", "CODESCAN_ISSUE_VIEW_DETAILS"]
- },
- "CodeScanSucceededEvent": {
- "type": "structure",
- "required": ["programmingLanguage", "codeScanJobId", "timestamp", "numberOfFindings"],
- "members": {
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "codeScanJobId": {
- "shape": "CodeScanJobId"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "numberOfFindings": {
- "shape": "PrimitiveInteger"
- },
- "codeAnalysisScope": {
- "shape": "CodeAnalysisScope"
- }
- }
- },
- "Completion": {
- "type": "structure",
- "required": ["content"],
- "members": {
- "content": {
- "shape": "CompletionContentString"
- },
- "references": {
- "shape": "References"
- },
- "mostRelevantMissingImports": {
- "shape": "Imports"
- }
- }
- },
- "CompletionContentString": {
- "type": "string",
- "max": 5120,
- "min": 1,
- "sensitive": true
- },
- "CompletionType": {
- "type": "string",
- "enum": ["BLOCK", "LINE"]
- },
- "Completions": {
- "type": "list",
- "member": {
- "shape": "Completion"
- },
- "max": 10,
- "min": 0
- },
- "ConflictException": {
- "type": "structure",
- "required": ["message"],
- "members": {
- "message": {
- "shape": "String"
- },
- "reason": {
- "shape": "ConflictExceptionReason"
- }
- },
- "exception": true
- },
- "ConflictExceptionReason": {
- "type": "string",
- "enum": ["CUSTOMER_KMS_KEY_INVALID_KEY_POLICY", "CUSTOMER_KMS_KEY_DISABLED", "MISMATCHED_KMS_KEY"]
- },
- "ConsoleState": {
- "type": "structure",
- "members": {
- "region": {
- "shape": "String"
- },
- "consoleUrl": {
- "shape": "SensitiveString"
- },
- "serviceId": {
- "shape": "String"
- },
- "serviceConsolePage": {
- "shape": "String"
- },
- "serviceSubconsolePage": {
- "shape": "String"
- },
- "taskName": {
- "shape": "SensitiveString"
- }
- }
- },
- "ContentChecksumType": {
- "type": "string",
- "enum": ["SHA_256"]
- },
- "ContextTruncationScheme": {
- "type": "string",
- "enum": ["ANALYSIS", "GUMBY"]
- },
- "ConversationId": {
- "type": "string",
- "max": 128,
- "min": 1
- },
- "ConversationState": {
- "type": "structure",
- "required": ["currentMessage", "chatTriggerType"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "history": {
- "shape": "ChatHistory"
- },
- "currentMessage": {
- "shape": "ChatMessage"
- },
- "chatTriggerType": {
- "shape": "ChatTriggerType"
- },
- "customizationArn": {
- "shape": "ResourceArn"
- }
- }
- },
- "CreateTaskAssistConversationRequest": {
- "type": "structure",
- "members": {
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "CreateTaskAssistConversationResponse": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- }
- }
- },
- "CreateUploadUrlRequest": {
- "type": "structure",
- "members": {
- "contentMd5": {
- "shape": "CreateUploadUrlRequestContentMd5String"
- },
- "contentChecksum": {
- "shape": "CreateUploadUrlRequestContentChecksumString"
- },
- "contentChecksumType": {
- "shape": "ContentChecksumType"
- },
- "contentLength": {
- "shape": "CreateUploadUrlRequestContentLengthLong"
- },
- "artifactType": {
- "shape": "ArtifactType"
- },
- "uploadIntent": {
- "shape": "UploadIntent"
- },
- "uploadContext": {
- "shape": "UploadContext"
- },
- "uploadId": {
- "shape": "UploadId"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "CreateUploadUrlRequestContentChecksumString": {
- "type": "string",
- "max": 512,
- "min": 1,
- "sensitive": true
- },
- "CreateUploadUrlRequestContentLengthLong": {
- "type": "long",
- "box": true,
- "min": 1
- },
- "CreateUploadUrlRequestContentMd5String": {
- "type": "string",
- "max": 128,
- "min": 1,
- "sensitive": true
- },
- "CreateUploadUrlResponse": {
- "type": "structure",
- "required": ["uploadId", "uploadUrl"],
- "members": {
- "uploadId": {
- "shape": "UploadId"
- },
- "uploadUrl": {
- "shape": "PreSignedUrl"
- },
- "kmsKeyArn": {
- "shape": "ResourceArn"
- },
- "requestHeaders": {
- "shape": "RequestHeaders"
- }
- }
- },
- "CreateUserMemoryEntryInput": {
- "type": "structure",
- "required": ["memoryEntryString", "origin"],
- "members": {
- "memoryEntryString": {
- "shape": "CreateUserMemoryEntryInputMemoryEntryStringString"
- },
- "origin": {
- "shape": "Origin"
- },
- "profileArn": {
- "shape": "CreateUserMemoryEntryInputProfileArnString"
- },
- "clientToken": {
- "shape": "String",
- "idempotencyToken": true
- }
- }
- },
- "CreateUserMemoryEntryInputMemoryEntryStringString": {
- "type": "string",
- "min": 1,
- "sensitive": true
- },
- "CreateUserMemoryEntryInputProfileArnString": {
- "type": "string",
- "min": 1,
- "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}"
- },
- "CreateUserMemoryEntryOutput": {
- "type": "structure",
- "required": ["memoryEntry"],
- "members": {
- "memoryEntry": {
- "shape": "MemoryEntry"
- }
- }
- },
- "CreateWorkspaceRequest": {
- "type": "structure",
- "required": ["workspaceRoot"],
- "members": {
- "workspaceRoot": {
- "shape": "CreateWorkspaceRequestWorkspaceRootString"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "CreateWorkspaceRequestWorkspaceRootString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "CreateWorkspaceResponse": {
- "type": "structure",
- "required": ["workspace"],
- "members": {
- "workspace": {
- "shape": "WorkspaceMetadata"
- }
- }
- },
- "CursorState": {
- "type": "structure",
- "members": {
- "position": {
- "shape": "Position"
- },
- "range": {
- "shape": "Range"
- }
- },
- "union": true
- },
- "Customization": {
- "type": "structure",
- "required": ["arn"],
- "members": {
- "arn": {
- "shape": "CustomizationArn"
- },
- "name": {
- "shape": "CustomizationName"
- },
- "description": {
- "shape": "Description"
- }
- }
- },
- "CustomizationArn": {
- "type": "string",
- "max": 950,
- "min": 0,
- "pattern": "arn:[-.a-z0-9]{1,63}:codewhisperer:([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}"
- },
- "CustomizationName": {
- "type": "string",
- "max": 100,
- "min": 1,
- "pattern": "[a-zA-Z][a-zA-Z0-9_-]*"
- },
- "Customizations": {
- "type": "list",
- "member": {
- "shape": "Customization"
- }
- },
- "DashboardAnalytics": {
- "type": "structure",
- "required": ["toggle"],
- "members": {
- "toggle": {
- "shape": "OptInFeatureToggle"
- }
- }
- },
- "DeleteTaskAssistConversationRequest": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "DeleteTaskAssistConversationResponse": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- }
- }
- },
- "DeleteUserMemoryEntryInput": {
- "type": "structure",
- "required": ["id"],
- "members": {
- "id": {
- "shape": "DeleteUserMemoryEntryInputIdString"
- },
- "profileArn": {
- "shape": "DeleteUserMemoryEntryInputProfileArnString"
- }
- }
- },
- "DeleteUserMemoryEntryInputIdString": {
- "type": "string",
- "max": 36,
- "min": 36,
- "pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
- },
- "DeleteUserMemoryEntryInputProfileArnString": {
- "type": "string",
- "min": 1,
- "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}"
- },
- "DeleteUserMemoryEntryOutput": {
- "type": "structure",
- "members": {}
- },
- "DeleteWorkspaceRequest": {
- "type": "structure",
- "required": ["workspaceId"],
- "members": {
- "workspaceId": {
- "shape": "UUID"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "DeleteWorkspaceResponse": {
- "type": "structure",
- "members": {}
- },
- "Description": {
- "type": "string",
- "max": 256,
- "min": 0,
- "pattern": "[\\sa-zA-Z0-9_-]*"
- },
- "Diagnostic": {
- "type": "structure",
- "members": {
- "textDocumentDiagnostic": {
- "shape": "TextDocumentDiagnostic"
- },
- "runtimeDiagnostic": {
- "shape": "RuntimeDiagnostic"
- }
- },
- "union": true
- },
- "DiagnosticLocation": {
- "type": "structure",
- "required": ["uri", "range"],
- "members": {
- "uri": {
- "shape": "DiagnosticLocationUriString"
- },
- "range": {
- "shape": "Range"
- }
- }
- },
- "DiagnosticLocationUriString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "DiagnosticRelatedInformation": {
- "type": "structure",
- "required": ["location", "message"],
- "members": {
- "location": {
- "shape": "DiagnosticLocation"
- },
- "message": {
- "shape": "DiagnosticRelatedInformationMessageString"
- }
- }
- },
- "DiagnosticRelatedInformationList": {
- "type": "list",
- "member": {
- "shape": "DiagnosticRelatedInformation"
- },
- "max": 1024,
- "min": 0
- },
- "DiagnosticRelatedInformationMessageString": {
- "type": "string",
- "max": 1024,
- "min": 0,
- "sensitive": true
- },
- "DiagnosticSeverity": {
- "type": "string",
- "enum": ["ERROR", "WARNING", "INFORMATION", "HINT"]
- },
- "DiagnosticTag": {
- "type": "string",
- "enum": ["UNNECESSARY", "DEPRECATED"]
- },
- "DiagnosticTagList": {
- "type": "list",
- "member": {
- "shape": "DiagnosticTag"
- },
- "max": 1024,
- "min": 0
- },
- "Dimension": {
- "type": "structure",
- "members": {
- "name": {
- "shape": "DimensionNameString"
- },
- "value": {
- "shape": "DimensionValueString"
- }
- }
- },
- "DimensionList": {
- "type": "list",
- "member": {
- "shape": "Dimension"
- },
- "max": 30,
- "min": 0
- },
- "DimensionNameString": {
- "type": "string",
- "max": 255,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "DimensionValueString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "DocFolderLevel": {
- "type": "string",
- "enum": ["SUB_FOLDER", "ENTIRE_WORKSPACE"]
- },
- "DocGenerationEvent": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "numberOfAddChars": {
- "shape": "PrimitiveInteger"
- },
- "numberOfAddLines": {
- "shape": "PrimitiveInteger"
- },
- "numberOfAddFiles": {
- "shape": "PrimitiveInteger"
- },
- "userDecision": {
- "shape": "DocUserDecision"
- },
- "interactionType": {
- "shape": "DocInteractionType"
- },
- "userIdentity": {
- "shape": "String"
- },
- "numberOfNavigation": {
- "shape": "PrimitiveInteger"
- },
- "folderLevel": {
- "shape": "DocFolderLevel"
- }
- }
- },
- "DocInteractionType": {
- "type": "string",
- "enum": ["GENERATE_README", "UPDATE_README", "EDIT_README"]
- },
- "DocUserDecision": {
- "type": "string",
- "enum": ["ACCEPT", "REJECT"]
- },
- "DocV2AcceptanceEvent": {
- "type": "structure",
- "required": [
- "conversationId",
- "numberOfAddedChars",
- "numberOfAddedLines",
- "numberOfAddedFiles",
- "userDecision",
- "interactionType",
- "numberOfNavigations",
- "folderLevel"
- ],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "numberOfAddedChars": {
- "shape": "DocV2AcceptanceEventNumberOfAddedCharsInteger"
- },
- "numberOfAddedLines": {
- "shape": "DocV2AcceptanceEventNumberOfAddedLinesInteger"
- },
- "numberOfAddedFiles": {
- "shape": "DocV2AcceptanceEventNumberOfAddedFilesInteger"
- },
- "userDecision": {
- "shape": "DocUserDecision"
- },
- "interactionType": {
- "shape": "DocInteractionType"
- },
- "numberOfNavigations": {
- "shape": "DocV2AcceptanceEventNumberOfNavigationsInteger"
- },
- "folderLevel": {
- "shape": "DocFolderLevel"
- }
- }
- },
- "DocV2AcceptanceEventNumberOfAddedCharsInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2AcceptanceEventNumberOfAddedFilesInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2AcceptanceEventNumberOfAddedLinesInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2AcceptanceEventNumberOfNavigationsInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2GenerationEvent": {
- "type": "structure",
- "required": [
- "conversationId",
- "numberOfGeneratedChars",
- "numberOfGeneratedLines",
- "numberOfGeneratedFiles"
- ],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "numberOfGeneratedChars": {
- "shape": "DocV2GenerationEventNumberOfGeneratedCharsInteger"
- },
- "numberOfGeneratedLines": {
- "shape": "DocV2GenerationEventNumberOfGeneratedLinesInteger"
- },
- "numberOfGeneratedFiles": {
- "shape": "DocV2GenerationEventNumberOfGeneratedFilesInteger"
- },
- "interactionType": {
- "shape": "DocInteractionType"
- },
- "numberOfNavigations": {
- "shape": "DocV2GenerationEventNumberOfNavigationsInteger"
- },
- "folderLevel": {
- "shape": "DocFolderLevel"
- }
- }
- },
- "DocV2GenerationEventNumberOfGeneratedCharsInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2GenerationEventNumberOfGeneratedFilesInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2GenerationEventNumberOfGeneratedLinesInteger": {
- "type": "integer",
- "min": 0
- },
- "DocV2GenerationEventNumberOfNavigationsInteger": {
- "type": "integer",
- "min": 0
- },
- "DocumentSymbol": {
- "type": "structure",
- "required": ["name", "type"],
- "members": {
- "name": {
- "shape": "DocumentSymbolNameString"
- },
- "type": {
- "shape": "SymbolType"
- },
- "source": {
- "shape": "DocumentSymbolSourceString"
- }
- }
- },
- "DocumentSymbolNameString": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "DocumentSymbolSourceString": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "DocumentSymbols": {
- "type": "list",
- "member": {
- "shape": "DocumentSymbol"
- },
- "max": 1000,
- "min": 0
- },
- "DocumentationIntentContext": {
- "type": "structure",
- "required": ["type"],
- "members": {
- "scope": {
- "shape": "DocumentationIntentContextScopeString"
- },
- "type": {
- "shape": "DocumentationType"
- }
- }
- },
- "DocumentationIntentContextScopeString": {
- "type": "string",
- "max": 4096,
- "min": 1,
- "sensitive": true
- },
- "DocumentationType": {
- "type": "string",
- "enum": ["README"]
- },
- "Double": {
- "type": "double",
- "box": true
- },
- "EditorState": {
- "type": "structure",
- "members": {
- "document": {
- "shape": "TextDocument"
- },
- "cursorState": {
- "shape": "CursorState"
- },
- "relevantDocuments": {
- "shape": "RelevantDocumentList"
- },
- "useRelevantDocuments": {
- "shape": "Boolean"
- },
- "workspaceFolders": {
- "shape": "WorkspaceFolderList"
- }
- }
- },
- "EnvState": {
- "type": "structure",
- "members": {
- "operatingSystem": {
- "shape": "EnvStateOperatingSystemString"
- },
- "currentWorkingDirectory": {
- "shape": "EnvStateCurrentWorkingDirectoryString"
- },
- "environmentVariables": {
- "shape": "EnvironmentVariables"
- },
- "timezoneOffset": {
- "shape": "EnvStateTimezoneOffsetInteger"
- }
- }
- },
- "EnvStateCurrentWorkingDirectoryString": {
- "type": "string",
- "max": 256,
- "min": 1,
- "sensitive": true
- },
- "EnvStateOperatingSystemString": {
- "type": "string",
- "max": 32,
- "min": 1,
- "pattern": "(macos|linux|windows)"
- },
- "EnvStateTimezoneOffsetInteger": {
- "type": "integer",
- "box": true,
- "max": 1440,
- "min": -1440
- },
- "EnvironmentVariable": {
- "type": "structure",
- "members": {
- "key": {
- "shape": "EnvironmentVariableKeyString"
- },
- "value": {
- "shape": "EnvironmentVariableValueString"
- }
- }
- },
- "EnvironmentVariableKeyString": {
- "type": "string",
- "max": 256,
- "min": 1,
- "sensitive": true
- },
- "EnvironmentVariableValueString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "EnvironmentVariables": {
- "type": "list",
- "member": {
- "shape": "EnvironmentVariable"
- },
- "max": 100,
- "min": 0
- },
- "ErrorDetails": {
- "type": "string",
- "max": 2048,
- "min": 0
- },
- "Event": {
- "type": "structure",
- "required": ["eventId", "generationId", "eventTimestamp", "eventType", "eventBlob"],
- "members": {
- "eventId": {
- "shape": "UUID"
- },
- "generationId": {
- "shape": "UUID"
- },
- "eventTimestamp": {
- "shape": "SyntheticTimestamp_date_time"
- },
- "eventType": {
- "shape": "EventType"
- },
- "eventBlob": {
- "shape": "EventBlob"
- }
- }
- },
- "EventBlob": {
- "type": "blob",
- "max": 400000,
- "min": 1,
- "sensitive": true
- },
- "EventList": {
- "type": "list",
- "member": {
- "shape": "Event"
- },
- "max": 10,
- "min": 1
- },
- "EventType": {
- "type": "string",
- "max": 100,
- "min": 1
- },
- "ExternalIdentityDetails": {
- "type": "structure",
- "members": {
- "issuerUrl": {
- "shape": "IssuerUrl"
- },
- "clientId": {
- "shape": "ClientId"
- },
- "scimEndpoint": {
- "shape": "String"
- }
- }
- },
- "FeatureDevCodeAcceptanceEvent": {
- "type": "structure",
- "required": ["conversationId", "linesOfCodeAccepted", "charactersOfCodeAccepted"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "linesOfCodeAccepted": {
- "shape": "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger"
- },
- "charactersOfCodeAccepted": {
- "shape": "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- }
- }
- },
- "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger": {
- "type": "integer",
- "min": 0
- },
- "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger": {
- "type": "integer",
- "min": 0
- },
- "FeatureDevCodeGenerationEvent": {
- "type": "structure",
- "required": ["conversationId", "linesOfCodeGenerated", "charactersOfCodeGenerated"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "linesOfCodeGenerated": {
- "shape": "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger"
- },
- "charactersOfCodeGenerated": {
- "shape": "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- }
- }
- },
- "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger": {
- "type": "integer",
- "min": 0
- },
- "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger": {
- "type": "integer",
- "min": 0
- },
- "FeatureDevEvent": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- }
- }
- },
- "FeatureEvaluation": {
- "type": "structure",
- "required": ["feature", "variation", "value"],
- "members": {
- "feature": {
- "shape": "FeatureName"
- },
- "variation": {
- "shape": "FeatureVariation"
- },
- "value": {
- "shape": "FeatureValue"
- }
- }
- },
- "FeatureEvaluationsList": {
- "type": "list",
- "member": {
- "shape": "FeatureEvaluation"
- },
- "max": 50,
- "min": 0
- },
- "FeatureName": {
- "type": "string",
- "max": 128,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "FeatureValue": {
- "type": "structure",
- "members": {
- "boolValue": {
- "shape": "Boolean"
- },
- "doubleValue": {
- "shape": "Double"
- },
- "longValue": {
- "shape": "Long"
- },
- "stringValue": {
- "shape": "FeatureValueStringType"
- }
- },
- "union": true
- },
- "FeatureValueStringType": {
- "type": "string",
- "max": 512,
- "min": 0
- },
- "FeatureVariation": {
- "type": "string",
- "max": 128,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "FileContext": {
- "type": "structure",
- "required": ["leftFileContent", "rightFileContent", "filename", "programmingLanguage"],
- "members": {
- "leftFileContent": {
- "shape": "FileContextLeftFileContentString"
- },
- "rightFileContent": {
- "shape": "FileContextRightFileContentString"
- },
- "filename": {
- "shape": "FileContextFilenameString"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- }
- }
- },
- "FileContextFilenameString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "FileContextLeftFileContentString": {
- "type": "string",
- "max": 10240,
- "min": 0,
- "sensitive": true
- },
- "FileContextRightFileContentString": {
- "type": "string",
- "max": 10240,
- "min": 0,
- "sensitive": true
- },
- "FollowupPrompt": {
- "type": "structure",
- "required": ["content"],
- "members": {
- "content": {
- "shape": "FollowupPromptContentString"
- },
- "userIntent": {
- "shape": "UserIntent"
- }
- }
- },
- "FollowupPromptContentString": {
- "type": "string",
- "max": 4096,
- "min": 0,
- "sensitive": true
- },
- "FunctionalityName": {
- "type": "string",
- "enum": [
- "COMPLETIONS",
- "ANALYSIS",
- "CONVERSATIONS",
- "TASK_ASSIST",
- "TRANSFORMATIONS",
- "CHAT_CUSTOMIZATION",
- "TRANSFORMATIONS_WEBAPP",
- "FEATURE_DEVELOPMENT"
- ],
- "max": 64,
- "min": 1
- },
- "GenerateCompletionsRequest": {
- "type": "structure",
- "required": ["fileContext"],
- "members": {
- "fileContext": {
- "shape": "FileContext"
- },
- "maxResults": {
- "shape": "GenerateCompletionsRequestMaxResultsInteger"
- },
- "nextToken": {
- "shape": "GenerateCompletionsRequestNextTokenString"
- },
- "referenceTrackerConfiguration": {
- "shape": "ReferenceTrackerConfiguration"
- },
- "supplementalContexts": {
- "shape": "SupplementalContextList"
- },
- "customizationArn": {
- "shape": "CustomizationArn"
- },
- "optOutPreference": {
- "shape": "OptOutPreference"
- },
- "userContext": {
- "shape": "UserContext"
- },
- "profileArn": {
- "shape": "ProfileArn"
- },
- "workspaceId": {
- "shape": "UUID"
- }
- }
- },
- "GenerateCompletionsRequestMaxResultsInteger": {
- "type": "integer",
- "box": true,
- "max": 10,
- "min": 1
- },
- "GenerateCompletionsRequestNextTokenString": {
- "type": "string",
- "max": 2048,
- "min": 0,
- "pattern": "(?:[A-Za-z0-9\\+/]{4})*(?:[A-Za-z0-9\\+/]{2}\\=\\=|[A-Za-z0-9\\+/]{3}\\=)?",
- "sensitive": true
- },
- "GenerateCompletionsResponse": {
- "type": "structure",
- "members": {
- "completions": {
- "shape": "Completions"
- },
- "nextToken": {
- "shape": "SensitiveString"
- }
- }
- },
- "GetCodeAnalysisRequest": {
- "type": "structure",
- "required": ["jobId"],
- "members": {
- "jobId": {
- "shape": "GetCodeAnalysisRequestJobIdString"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetCodeAnalysisRequestJobIdString": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "GetCodeAnalysisResponse": {
- "type": "structure",
- "required": ["status"],
- "members": {
- "status": {
- "shape": "CodeAnalysisStatus"
- },
- "errorMessage": {
- "shape": "SensitiveString"
- }
- }
- },
- "GetCodeFixJobRequest": {
- "type": "structure",
- "required": ["jobId"],
- "members": {
- "jobId": {
- "shape": "GetCodeFixJobRequestJobIdString"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetCodeFixJobRequestJobIdString": {
- "type": "string",
- "max": 256,
- "min": 1,
- "pattern": ".*[A-Za-z0-9-:]+.*"
- },
- "GetCodeFixJobResponse": {
- "type": "structure",
- "members": {
- "jobStatus": {
- "shape": "CodeFixJobStatus"
- },
- "suggestedFix": {
- "shape": "SuggestedFix"
- }
- }
- },
- "GetTaskAssistCodeGenerationRequest": {
- "type": "structure",
- "required": ["conversationId", "codeGenerationId"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "codeGenerationId": {
- "shape": "CodeGenerationId"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetTaskAssistCodeGenerationResponse": {
- "type": "structure",
- "required": ["conversationId", "codeGenerationStatus"],
- "members": {
- "conversationId": {
- "shape": "ConversationId"
- },
- "codeGenerationStatus": {
- "shape": "CodeGenerationStatus"
- },
- "codeGenerationStatusDetail": {
- "shape": "CodeGenerationStatusDetail"
- },
- "codeGenerationRemainingIterationCount": {
- "shape": "Integer"
- },
- "codeGenerationTotalIterationCount": {
- "shape": "Integer"
- }
- }
- },
- "GetTestGenerationRequest": {
- "type": "structure",
- "required": ["testGenerationJobGroupName", "testGenerationJobId"],
- "members": {
- "testGenerationJobGroupName": {
- "shape": "TestGenerationJobGroupName"
- },
- "testGenerationJobId": {
- "shape": "UUID"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetTestGenerationResponse": {
- "type": "structure",
- "members": {
- "testGenerationJob": {
- "shape": "TestGenerationJob"
- }
- }
- },
- "GetTransformationPlanRequest": {
- "type": "structure",
- "required": ["transformationJobId"],
- "members": {
- "transformationJobId": {
- "shape": "TransformationJobId"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetTransformationPlanResponse": {
- "type": "structure",
- "required": ["transformationPlan"],
- "members": {
- "transformationPlan": {
- "shape": "TransformationPlan"
- }
- }
- },
- "GetTransformationRequest": {
- "type": "structure",
- "required": ["transformationJobId"],
- "members": {
- "transformationJobId": {
- "shape": "TransformationJobId"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "GetTransformationResponse": {
- "type": "structure",
- "required": ["transformationJob"],
- "members": {
- "transformationJob": {
- "shape": "TransformationJob"
- }
- }
- },
- "GitState": {
- "type": "structure",
- "members": {
- "status": {
- "shape": "GitStateStatusString"
- }
- }
- },
- "GitStateStatusString": {
- "type": "string",
- "max": 4096,
- "min": 0,
- "sensitive": true
- },
- "IdeCategory": {
- "type": "string",
- "enum": ["JETBRAINS", "VSCODE", "CLI", "JUPYTER_MD", "JUPYTER_SM", "ECLIPSE", "VISUAL_STUDIO"],
- "max": 64,
- "min": 1
- },
- "IdeDiagnostic": {
- "type": "structure",
- "required": ["ideDiagnosticType"],
- "members": {
- "range": {
- "shape": "Range"
- },
- "source": {
- "shape": "IdeDiagnosticSourceString"
- },
- "severity": {
- "shape": "DiagnosticSeverity"
- },
- "ideDiagnosticType": {
- "shape": "IdeDiagnosticType"
- }
- }
- },
- "IdeDiagnosticList": {
- "type": "list",
- "member": {
- "shape": "IdeDiagnostic"
- },
- "max": 1024,
- "min": 0
- },
- "IdeDiagnosticSourceString": {
- "type": "string",
- "max": 1024,
- "min": 0,
- "sensitive": true
- },
- "IdeDiagnosticType": {
- "type": "string",
- "enum": ["SYNTAX_ERROR", "TYPE_ERROR", "REFERENCE_ERROR", "BEST_PRACTICE", "SECURITY", "OTHER"]
- },
- "IdempotencyToken": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "IdentityDetails": {
- "type": "structure",
- "members": {
- "ssoIdentityDetails": {
- "shape": "SSOIdentityDetails"
- },
- "externalIdentityDetails": {
- "shape": "ExternalIdentityDetails"
- }
- },
- "union": true
- },
- "ImageBlock": {
- "type": "structure",
- "required": ["format", "source"],
- "members": {
- "format": {
- "shape": "ImageFormat"
- },
- "source": {
- "shape": "ImageSource"
- }
- }
- },
- "ImageBlocks": {
- "type": "list",
- "member": {
- "shape": "ImageBlock"
- },
- "max": 10,
- "min": 0
- },
- "ImageFormat": {
- "type": "string",
- "enum": ["png", "jpeg", "gif", "webp"]
- },
- "ImageSource": {
- "type": "structure",
- "members": {
- "bytes": {
- "shape": "ImageSourceBytesBlob"
- }
- },
- "sensitive": true,
- "union": true
- },
- "ImageSourceBytesBlob": {
- "type": "blob",
- "max": 1500000,
- "min": 1
- },
- "Import": {
- "type": "structure",
- "members": {
- "statement": {
- "shape": "ImportStatementString"
- }
- }
- },
- "ImportStatementString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "Imports": {
- "type": "list",
- "member": {
- "shape": "Import"
- },
- "max": 10,
- "min": 0
- },
- "InlineChatEvent": {
- "type": "structure",
- "required": ["requestId", "timestamp"],
- "members": {
- "requestId": {
- "shape": "UUID"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "inputLength": {
- "shape": "PrimitiveInteger"
- },
- "numSelectedLines": {
- "shape": "PrimitiveInteger"
- },
- "numSuggestionAddChars": {
- "shape": "PrimitiveInteger"
- },
- "numSuggestionAddLines": {
- "shape": "PrimitiveInteger"
- },
- "numSuggestionDelChars": {
- "shape": "PrimitiveInteger"
- },
- "numSuggestionDelLines": {
- "shape": "PrimitiveInteger"
- },
- "codeIntent": {
- "shape": "Boolean"
- },
- "userDecision": {
- "shape": "InlineChatUserDecision"
- },
- "responseStartLatency": {
- "shape": "Double"
- },
- "responseEndLatency": {
- "shape": "Double"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- }
- }
- },
- "InlineChatUserDecision": {
- "type": "string",
- "enum": ["ACCEPT", "REJECT", "DISMISS"]
- },
- "Integer": {
- "type": "integer",
- "box": true
- },
- "Intent": {
- "type": "string",
- "enum": ["DEV", "DOC"]
- },
- "IntentContext": {
- "type": "structure",
- "members": {
- "documentation": {
- "shape": "DocumentationIntentContext"
- }
- },
- "union": true
- },
- "InternalServerException": {
- "type": "structure",
- "required": ["message"],
- "members": {
- "message": {
- "shape": "String"
- }
- },
- "exception": true,
- "fault": true,
- "retryable": {
- "throttling": false
- }
- },
- "IssuerUrl": {
- "type": "string",
- "max": 255,
- "min": 1
- },
- "LineRangeList": {
- "type": "list",
- "member": {
- "shape": "Range"
- }
- },
- "ListAvailableCustomizationsRequest": {
- "type": "structure",
- "members": {
- "maxResults": {
- "shape": "ListAvailableCustomizationsRequestMaxResultsInteger"
- },
- "nextToken": {
- "shape": "Base64EncodedPaginationToken"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "ListAvailableCustomizationsRequestMaxResultsInteger": {
- "type": "integer",
- "box": true,
- "max": 100,
- "min": 1
- },
- "ListAvailableCustomizationsResponse": {
- "type": "structure",
- "required": ["customizations"],
- "members": {
- "customizations": {
- "shape": "Customizations"
- },
- "nextToken": {
- "shape": "Base64EncodedPaginationToken"
- }
- }
- },
- "ListAvailableProfilesRequest": {
- "type": "structure",
- "members": {
- "maxResults": {
- "shape": "ListAvailableProfilesRequestMaxResultsInteger"
- },
- "nextToken": {
- "shape": "Base64EncodedPaginationToken"
- }
- }
- },
- "ListAvailableProfilesRequestMaxResultsInteger": {
- "type": "integer",
- "box": true,
- "max": 10,
- "min": 1
- },
- "ListAvailableProfilesResponse": {
- "type": "structure",
- "required": ["profiles"],
- "members": {
- "profiles": {
- "shape": "ProfileList"
- },
- "nextToken": {
- "shape": "Base64EncodedPaginationToken"
- }
- }
- },
- "ListCodeAnalysisFindingsRequest": {
- "type": "structure",
- "required": ["jobId", "codeAnalysisFindingsSchema"],
- "members": {
- "jobId": {
- "shape": "ListCodeAnalysisFindingsRequestJobIdString"
- },
- "nextToken": {
- "shape": "PaginationToken"
- },
- "codeAnalysisFindingsSchema": {
- "shape": "CodeAnalysisFindingsSchema"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "ListCodeAnalysisFindingsRequestJobIdString": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "ListCodeAnalysisFindingsResponse": {
- "type": "structure",
- "required": ["codeAnalysisFindings"],
- "members": {
- "nextToken": {
- "shape": "PaginationToken"
- },
- "codeAnalysisFindings": {
- "shape": "SensitiveString"
- }
- }
- },
- "ListEventsRequest": {
- "type": "structure",
- "required": ["conversationId"],
- "members": {
- "conversationId": {
- "shape": "UUID"
- },
- "maxResults": {
- "shape": "ListEventsRequestMaxResultsInteger"
- },
- "nextToken": {
- "shape": "NextToken"
- }
- }
- },
- "ListEventsRequestMaxResultsInteger": {
- "type": "integer",
- "box": true,
- "max": 50,
- "min": 1
- },
- "ListEventsResponse": {
- "type": "structure",
- "required": ["conversationId", "events"],
- "members": {
- "conversationId": {
- "shape": "UUID"
- },
- "events": {
- "shape": "EventList"
- },
- "nextToken": {
- "shape": "NextToken"
- }
- }
- },
- "ListFeatureEvaluationsRequest": {
- "type": "structure",
- "required": ["userContext"],
- "members": {
- "userContext": {
- "shape": "UserContext"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "ListFeatureEvaluationsResponse": {
- "type": "structure",
- "required": ["featureEvaluations"],
- "members": {
- "featureEvaluations": {
- "shape": "FeatureEvaluationsList"
- }
- }
- },
- "ListUserMemoryEntriesInput": {
- "type": "structure",
- "members": {
- "maxResults": {
- "shape": "ListUserMemoryEntriesInputMaxResultsInteger"
- },
- "profileArn": {
- "shape": "ListUserMemoryEntriesInputProfileArnString"
- },
- "nextToken": {
- "shape": "ListUserMemoryEntriesInputNextTokenString"
- }
- }
- },
- "ListUserMemoryEntriesInputMaxResultsInteger": {
- "type": "integer",
- "box": true,
- "max": 100,
- "min": 1
- },
- "ListUserMemoryEntriesInputNextTokenString": {
- "type": "string",
- "min": 1
- },
- "ListUserMemoryEntriesInputProfileArnString": {
- "type": "string",
- "min": 1,
- "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}"
- },
- "ListUserMemoryEntriesOutput": {
- "type": "structure",
- "required": ["memoryEntries"],
- "members": {
- "memoryEntries": {
- "shape": "MemoryEntryList"
- },
- "nextToken": {
- "shape": "ListUserMemoryEntriesOutputNextTokenString"
- }
- }
- },
- "ListUserMemoryEntriesOutputNextTokenString": {
- "type": "string",
- "min": 1
- },
- "ListWorkspaceMetadataRequest": {
- "type": "structure",
- "members": {
- "workspaceRoot": {
- "shape": "ListWorkspaceMetadataRequestWorkspaceRootString"
- },
- "nextToken": {
- "shape": "String"
- },
- "maxResults": {
- "shape": "Integer"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "ListWorkspaceMetadataRequestWorkspaceRootString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "sensitive": true
- },
- "ListWorkspaceMetadataResponse": {
- "type": "structure",
- "required": ["workspaces"],
- "members": {
- "workspaces": {
- "shape": "WorkspaceList"
- },
- "nextToken": {
- "shape": "String"
- }
- }
- },
- "Long": {
- "type": "long",
- "box": true
- },
- "MemoryEntry": {
- "type": "structure",
- "required": ["id", "memoryEntryString", "metadata"],
- "members": {
- "id": {
- "shape": "MemoryEntryIdString"
- },
- "memoryEntryString": {
- "shape": "MemoryEntryMemoryEntryStringString"
- },
- "metadata": {
- "shape": "MemoryEntryMetadata"
- }
- }
- },
- "MemoryEntryIdString": {
- "type": "string",
- "max": 36,
- "min": 36,
- "pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
- },
- "MemoryEntryList": {
- "type": "list",
- "member": {
- "shape": "MemoryEntry"
- }
- },
- "MemoryEntryMemoryEntryStringString": {
- "type": "string",
- "max": 500,
- "min": 1,
- "sensitive": true
- },
- "MemoryEntryMetadata": {
- "type": "structure",
- "required": ["origin", "createdAt", "updatedAt"],
- "members": {
- "origin": {
- "shape": "Origin"
- },
- "attributes": {
- "shape": "AttributesMap"
- },
- "createdAt": {
- "shape": "Timestamp"
- },
- "updatedAt": {
- "shape": "Timestamp"
- }
- }
- },
- "MessageId": {
- "type": "string",
- "max": 128,
- "min": 0
- },
- "MetricData": {
- "type": "structure",
- "required": ["metricName", "metricValue", "timestamp", "product"],
- "members": {
- "metricName": {
- "shape": "MetricDataMetricNameString"
- },
- "metricValue": {
- "shape": "Double"
- },
- "timestamp": {
- "shape": "Timestamp"
- },
- "product": {
- "shape": "MetricDataProductString"
- },
- "dimensions": {
- "shape": "DimensionList"
- }
- }
- },
- "MetricDataMetricNameString": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "MetricDataProductString": {
- "type": "string",
- "max": 128,
- "min": 1,
- "pattern": "[-a-zA-Z0-9._]*"
- },
- "NextToken": {
- "type": "string",
- "max": 1000,
- "min": 0
- },
- "Notifications": {
- "type": "list",
- "member": {
- "shape": "NotificationsFeature"
- },
- "max": 10,
- "min": 0
- },
- "NotificationsFeature": {
- "type": "structure",
- "required": ["feature", "toggle"],
- "members": {
- "feature": {
- "shape": "FeatureName"
- },
- "toggle": {
- "shape": "OptInFeatureToggle"
- }
- }
- },
- "OperatingSystem": {
- "type": "string",
- "enum": ["MAC", "WINDOWS", "LINUX"],
- "max": 64,
- "min": 1
- },
- "OptInFeatureToggle": {
- "type": "string",
- "enum": ["ON", "OFF"]
- },
- "OptInFeatures": {
- "type": "structure",
- "members": {
- "promptLogging": {
- "shape": "PromptLogging"
- },
- "byUserAnalytics": {
- "shape": "ByUserAnalytics"
- },
- "dashboardAnalytics": {
- "shape": "DashboardAnalytics"
- },
- "notifications": {
- "shape": "Notifications"
- },
- "workspaceContext": {
- "shape": "WorkspaceContext"
- }
- }
- },
- "OptOutPreference": {
- "type": "string",
- "enum": ["OPTIN", "OPTOUT"]
- },
- "Origin": {
- "type": "string",
- "enum": [
- "CHATBOT",
- "CONSOLE",
- "DOCUMENTATION",
- "MARKETING",
- "MOBILE",
- "SERVICE_INTERNAL",
- "UNIFIED_SEARCH",
- "UNKNOWN",
- "MD",
- "IDE",
- "SAGE_MAKER",
- "CLI",
- "AI_EDITOR",
- "OPENSEARCH_DASHBOARD",
- "GITLAB"
- ]
- },
- "PackageInfo": {
- "type": "structure",
- "members": {
- "executionCommand": {
- "shape": "SensitiveString"
- },
- "buildCommand": {
- "shape": "SensitiveString"
- },
- "buildOrder": {
- "shape": "PackageInfoBuildOrderInteger"
- },
- "testFramework": {
- "shape": "String"
- },
- "packageSummary": {
- "shape": "PackageInfoPackageSummaryString"
- },
- "packagePlan": {
- "shape": "PackageInfoPackagePlanString"
- },
- "targetFileInfoList": {
- "shape": "TargetFileInfoList"
- }
- }
- },
- "PackageInfoBuildOrderInteger": {
- "type": "integer",
- "box": true,
- "min": 0
- },
- "PackageInfoList": {
- "type": "list",
- "member": {
- "shape": "PackageInfo"
- }
- },
- "PackageInfoPackagePlanString": {
- "type": "string",
- "max": 30720,
- "min": 0,
- "sensitive": true
- },
- "PackageInfoPackageSummaryString": {
- "type": "string",
- "max": 30720,
- "min": 0,
- "sensitive": true
- },
- "PaginationToken": {
- "type": "string",
- "max": 2048,
- "min": 1,
- "pattern": "\\S+"
- },
- "Position": {
- "type": "structure",
- "required": ["line", "character"],
- "members": {
- "line": {
- "shape": "Integer"
- },
- "character": {
- "shape": "Integer"
- }
- }
- },
- "PreSignedUrl": {
- "type": "string",
- "max": 2048,
- "min": 1,
- "sensitive": true
- },
- "PrimitiveInteger": {
- "type": "integer"
- },
- "Profile": {
- "type": "structure",
- "required": ["arn", "profileName"],
- "members": {
- "arn": {
- "shape": "ProfileArn"
- },
- "identityDetails": {
- "shape": "IdentityDetails"
- },
- "profileName": {
- "shape": "ProfileName"
- },
- "description": {
- "shape": "ProfileDescription"
- },
- "referenceTrackerConfiguration": {
- "shape": "ReferenceTrackerConfiguration"
- },
- "kmsKeyArn": {
- "shape": "ResourceArn"
- },
- "activeFunctionalities": {
- "shape": "ActiveFunctionalityList"
- },
- "status": {
- "shape": "ProfileStatus"
- },
- "errorDetails": {
- "shape": "ErrorDetails"
- },
- "resourcePolicy": {
- "shape": "ResourcePolicy"
- },
- "profileType": {
- "shape": "ProfileType"
- },
- "optInFeatures": {
- "shape": "OptInFeatures"
- },
- "permissionUpdateRequired": {
- "shape": "Boolean"
- },
- "applicationProperties": {
- "shape": "ApplicationPropertiesList"
- }
- }
- },
- "ProfileArn": {
- "type": "string",
- "max": 950,
- "min": 0,
- "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}"
- },
- "ProfileDescription": {
- "type": "string",
- "max": 256,
- "min": 1,
- "pattern": "[\\sa-zA-Z0-9_-]*"
- },
- "ProfileList": {
- "type": "list",
- "member": {
- "shape": "Profile"
- }
- },
- "ProfileName": {
- "type": "string",
- "max": 100,
- "min": 1,
- "pattern": "[a-zA-Z][a-zA-Z0-9_-]*"
- },
- "ProfileStatus": {
- "type": "string",
- "enum": ["ACTIVE", "CREATING", "CREATE_FAILED", "UPDATING", "UPDATE_FAILED", "DELETING", "DELETE_FAILED"]
- },
- "ProfileType": {
- "type": "string",
- "enum": ["Q_DEVELOPER", "CODEWHISPERER"]
- },
- "ProgrammingLanguage": {
- "type": "structure",
- "required": ["languageName"],
- "members": {
- "languageName": {
- "shape": "ProgrammingLanguageLanguageNameString"
- }
- }
- },
- "ProgrammingLanguageLanguageNameString": {
- "type": "string",
- "max": 128,
- "min": 1,
- "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext|systemverilog|dart|lua|swift|powershell|r)"
- },
- "ProgressUpdates": {
- "type": "list",
- "member": {
- "shape": "TransformationProgressUpdate"
- }
- },
- "PromptLogging": {
- "type": "structure",
- "required": ["s3Uri", "toggle"],
- "members": {
- "s3Uri": {
- "shape": "S3Uri"
- },
- "toggle": {
- "shape": "OptInFeatureToggle"
- }
- }
- },
- "Range": {
- "type": "structure",
- "required": ["start", "end"],
- "members": {
- "start": {
- "shape": "Position"
- },
- "end": {
- "shape": "Position"
- }
- }
- },
- "RecommendationsWithReferencesPreference": {
- "type": "string",
- "enum": ["BLOCK", "ALLOW"]
- },
- "Reference": {
- "type": "structure",
- "members": {
- "licenseName": {
- "shape": "ReferenceLicenseNameString"
- },
- "repository": {
- "shape": "ReferenceRepositoryString"
- },
- "url": {
- "shape": "ReferenceUrlString"
- },
- "recommendationContentSpan": {
- "shape": "Span"
- }
- }
- },
- "ReferenceLicenseNameString": {
- "type": "string",
- "max": 1024,
- "min": 1
- },
- "ReferenceRepositoryString": {
- "type": "string",
- "max": 1024,
- "min": 1
- },
- "ReferenceTrackerConfiguration": {
- "type": "structure",
- "required": ["recommendationsWithReferences"],
- "members": {
- "recommendationsWithReferences": {
- "shape": "RecommendationsWithReferencesPreference"
- }
- }
- },
- "ReferenceUrlString": {
- "type": "string",
- "max": 1024,
- "min": 1
- },
- "References": {
- "type": "list",
- "member": {
- "shape": "Reference"
- },
- "max": 10,
- "min": 0
- },
- "RelevantDocumentList": {
- "type": "list",
- "member": {
- "shape": "RelevantTextDocument"
- },
- "max": 30,
- "min": 0
- },
- "RelevantTextDocument": {
- "type": "structure",
- "required": ["relativeFilePath"],
- "members": {
- "relativeFilePath": {
- "shape": "RelevantTextDocumentRelativeFilePathString"
- },
- "programmingLanguage": {
- "shape": "ProgrammingLanguage"
- },
- "text": {
- "shape": "RelevantTextDocumentTextString"
- },
- "documentSymbols": {
- "shape": "DocumentSymbols"
- }
- }
- },
- "RelevantTextDocumentRelativeFilePathString": {
- "type": "string",
- "max": 4096,
- "min": 1,
- "sensitive": true
- },
- "RelevantTextDocumentTextString": {
- "type": "string",
- "max": 40960,
- "min": 0,
- "sensitive": true
- },
- "RequestHeaderKey": {
- "type": "string",
- "max": 64,
- "min": 1
- },
- "RequestHeaderValue": {
- "type": "string",
- "max": 256,
- "min": 1
- },
- "RequestHeaders": {
- "type": "map",
- "key": {
- "shape": "RequestHeaderKey"
- },
- "value": {
- "shape": "RequestHeaderValue"
- },
- "max": 16,
- "min": 1,
- "sensitive": true
- },
- "ResourceArn": {
- "type": "string",
- "max": 1224,
- "min": 0,
- "pattern": "arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}"
- },
- "ResourceNotFoundException": {
- "type": "structure",
- "required": ["message"],
- "members": {
- "message": {
- "shape": "String"
- }
- },
- "exception": true
- },
- "ResourcePolicy": {
- "type": "structure",
- "required": ["effect"],
- "members": {
- "effect": {
- "shape": "ResourcePolicyEffect"
- }
- }
- },
- "ResourcePolicyEffect": {
- "type": "string",
- "enum": ["ALLOW", "DENY"]
- },
- "ResumeTransformationRequest": {
- "type": "structure",
- "required": ["transformationJobId"],
- "members": {
- "transformationJobId": {
- "shape": "TransformationJobId"
- },
- "userActionStatus": {
- "shape": "TransformationUserActionStatus"
- },
- "profileArn": {
- "shape": "ProfileArn"
- }
- }
- },
- "ResumeTransformationResponse": {
- "type": "structure",
- "required": ["transformationStatus"],
- "members": {
- "transformationStatus": {
- "shape": "TransformationStatus"
- }
- }
- },
- "RuntimeDiagnostic": {
- "type": "structure",
- "required": ["source", "severity", "message"],
- "members": {
- "source": {
- "shape": "RuntimeDiagnosticSourceString"
- },
- "severity": {
- "shape": "DiagnosticSeverity"
- },
- "message": {
- "shape": "RuntimeDiagnosticMessageString"
- }
- }
- },
- "RuntimeDiagnosticMessageString": {
- "type": "string",
- "max": 1024,
- "min": 0,
- "sensitive": true
- },
- "RuntimeDiagnosticSourceString": {
- "type": "string",
- "max": 1024,
- "min": 0,
- "sensitive": true
- },
- "S3Uri": {
- "type": "string",
- "max": 1024,
- "min": 1,
- "pattern": "s3://((?!xn--)[a-z0-9](?![^/]*[.]{2})[a-z0-9-.]{1,61}[a-z0-9](?): Promise {
- const bearerToken = await AuthUtil.instance.getBearerToken()
- const cwsprConfig = getCodewhispererConfig()
- return (await globals.sdkClientBuilder.createAwsService(
- Service,
- {
- apiConfig: apiConfig,
- region: cwsprConfig.region,
- endpoint: cwsprConfig.endpoint,
- token: new Token({ token: bearerToken }),
- httpOptions: {
- connectTimeout: 10000, // 10 seconds, 3 times P99 API latency
- },
- ...options,
- } as ServiceOptions,
- undefined
- )) as FeatureDevProxyClient
-}
-
-export class FeatureDevClient implements FeatureClient {
- public async getClient(options?: Partial) {
- // Should not be stored for the whole session.
- // Client has to be reinitialized for each request so we always have a fresh bearerToken
- return await createFeatureDevProxyClient(options)
- }
-
- public async createConversation() {
- try {
- const client = await this.getClient(writeAPIRetryOptions)
- getLogger().debug(`Executing createTaskAssistConversation with {}`)
- const { conversationId, $response } = await client
- .createTaskAssistConversation({
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- })
- .promise()
- getLogger().debug(`${featureName}: Created conversation: %O`, {
- conversationId,
- requestId: $response.requestId,
- })
- return conversationId
- } catch (e) {
- if (isAwsError(e)) {
- getLogger().error(
- `${featureName}: failed to start conversation: ${e.message} RequestId: ${e.requestId}`
- )
- // BE service will throw ServiceQuota if conversation limit is reached. API Front-end will throw Throttling with this message if conversation limit is reached
- if (
- e.code === 'ServiceQuotaExceededException' ||
- (e.code === 'ThrottlingException' && e.message.includes('reached for this month.'))
- ) {
- throw new MonthlyConversationLimitError(e.message)
- }
- throw ApiError.of(e.message, 'CreateConversation', e.code, e.statusCode ?? 500)
- }
-
- throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateConversation')
- }
- }
-
- public async createUploadUrl(
- conversationId: string,
- contentChecksumSha256: string,
- contentLength: number,
- uploadId: string
- ) {
- try {
- const client = await this.getClient(writeAPIRetryOptions)
- const params: CreateUploadUrlRequest = {
- uploadContext: {
- taskAssistPlanningUploadContext: {
- conversationId,
- },
- },
- uploadId,
- contentChecksum: contentChecksumSha256,
- contentChecksumType: 'SHA_256',
- artifactType: 'SourceCode',
- uploadIntent: 'TASK_ASSIST_PLANNING',
- contentLength,
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- }
- getLogger().debug(`Executing createUploadUrl with %O`, omit(params, 'contentChecksum'))
- const response = await client.createUploadUrl(params).promise()
- getLogger().debug(`${featureName}: Created upload url: %O`, {
- uploadId: uploadId,
- requestId: response.$response.requestId,
- })
- return response
- } catch (e) {
- if (isAwsError(e)) {
- getLogger().error(
- `${featureName}: failed to generate presigned url: ${e.message} RequestId: ${e.requestId}`
- )
- if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
- throw new ContentLengthError()
- }
- throw ApiError.of(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 500)
- }
-
- throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateUploadUrl')
- }
- }
-
- public async startCodeGeneration(
- conversationId: string,
- uploadId: string,
- message: string,
- intent: FeatureDevProxyClient.Intent,
- codeGenerationId: string,
- currentCodeGenerationId?: string,
- intentContext?: FeatureDevProxyClient.IntentContext
- ) {
- try {
- const client = await this.getClient(writeAPIRetryOptions)
- const params: StartTaskAssistCodeGenerationRequest = {
- codeGenerationId,
- conversationState: {
- conversationId,
- currentMessage: {
- userInputMessage: { content: message },
- },
- chatTriggerType: 'MANUAL',
- },
- workspaceState: {
- uploadId,
- programmingLanguage: { languageName: 'javascript' },
- },
- intent,
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- }
- if (currentCodeGenerationId) {
- params.currentCodeGenerationId = currentCodeGenerationId
- }
- if (intentContext) {
- params.intentContext = intentContext
- }
- getLogger().debug(`Executing startTaskAssistCodeGeneration with %O`, params)
- const response = await client.startTaskAssistCodeGeneration(params).promise()
-
- return response
- } catch (e) {
- getLogger().error(
- `${featureName}: failed to start code generation: ${(e as Error).message} RequestId: ${
- (e as any).requestId
- }`
- )
- if (isAwsError(e)) {
- // API Front-end will throw Throttling if conversation limit is reached. API Front-end monitors StartCodeGeneration for throttling
- if (e.code === 'ThrottlingException' && e.message.includes(startTaskAssistLimitReachedMessage)) {
- throw new MonthlyConversationLimitError(e.message)
- }
- // BE service will throw ServiceQuota if code generation iteration limit is reached
- else if (
- e.code === 'ServiceQuotaExceededException' ||
- (e.code === 'ThrottlingException' &&
- e.message.includes('limit for number of iterations on a code generation'))
- ) {
- throw new CodeIterationLimitError()
- }
- throw ApiError.of(e.message, 'StartTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
- }
-
- throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'StartTaskAssistCodeGeneration')
- }
- }
-
- public async getCodeGeneration(conversationId: string, codeGenerationId: string) {
- try {
- const client = await this.getClient()
- const params: GetTaskAssistCodeGenerationRequest = {
- codeGenerationId,
- conversationId,
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- }
- getLogger().debug(`Executing getTaskAssistCodeGeneration with %O`, params)
- const response = await client.getTaskAssistCodeGeneration(params).promise()
-
- return response
- } catch (e) {
- getLogger().error(
- `${featureName}: failed to start get code generation results: ${(e as Error).message} RequestId: ${
- (e as any).requestId
- }`
- )
-
- if (isAwsError(e)) {
- throw ApiError.of(e.message, 'GetTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
- }
-
- throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'GetTaskAssistCodeGeneration')
- }
- }
-
- public async exportResultArchive(conversationId: string) {
- const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile
- try {
- const streamingClient = await createCodeWhispererChatStreamingClient()
- const params = {
- exportId: conversationId,
- exportIntent: 'TASK_ASSIST',
- profileArn: profile?.arn,
- } satisfies ExportResultArchiveCommandInput
- getLogger().debug(`Executing exportResultArchive with %O`, params)
- const archiveResponse = await streamingClient.exportResultArchive(params)
- const buffer: number[] = []
- if (archiveResponse.body === undefined) {
- throw new ApiServiceError(
- 'Empty response from CodeWhisperer Streaming service.',
- 'ExportResultArchive',
- 'EmptyResponse',
- 500
- )
- }
- for await (const chunk of archiveResponse.body) {
- if (chunk.internalServerException !== undefined) {
- throw chunk.internalServerException
- }
- buffer.push(...(chunk.binaryPayloadEvent?.bytes ?? []))
- }
-
- const {
- code_generation_result: {
- new_file_contents: newFiles = {},
- deleted_files: deletedFiles = [],
- references = [],
- },
- } = JSON.parse(new TextDecoder().decode(Buffer.from(buffer))) as {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- code_generation_result: {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- new_file_contents?: Record
- // eslint-disable-next-line @typescript-eslint/naming-convention
- deleted_files?: string[]
- references?: CodeReference[]
- }
- }
- UserWrittenCodeTracker.instance.onQFeatureInvoked()
-
- const newFileContents: { zipFilePath: string; fileContent: string }[] = []
- for (const [filePath, fileContent] of Object.entries(newFiles)) {
- newFileContents.push({ zipFilePath: filePath, fileContent })
- }
-
- return { newFileContents, deletedFiles, references }
- } catch (e) {
- getLogger().error(
- `${featureName}: failed to export archive result: ${(e as Error).message} RequestId: ${
- (e as any).requestId
- }`
- )
-
- if (isAwsError(e)) {
- throw ApiError.of(e.message, 'ExportResultArchive', e.code, e.statusCode ?? 500)
- }
-
- throw new FeatureDevServiceError(e instanceof Error ? e.message : 'Unknown error', 'ExportResultArchive')
- }
- }
-
- /**
- * This event is specific to ABTesting purposes.
- *
- * No need to fail currently if the event fails in the request. In addition, currently there is no need for a return value.
- *
- * @param conversationId
- */
- public async sendFeatureDevTelemetryEvent(conversationId: string) {
- await this.sendFeatureDevEvent('featureDevEvent', {
- conversationId,
- })
- }
-
- public async sendFeatureDevCodeGenerationEvent(event: FeatureDevCodeGenerationEvent) {
- getLogger().debug(
- `featureDevCodeGenerationEvent: conversationId: ${event.conversationId} charactersOfCodeGenerated: ${event.charactersOfCodeGenerated} linesOfCodeGenerated: ${event.linesOfCodeGenerated}`
- )
- await this.sendFeatureDevEvent('featureDevCodeGenerationEvent', event)
- }
-
- public async sendFeatureDevCodeAcceptanceEvent(event: FeatureDevCodeAcceptanceEvent) {
- getLogger().debug(
- `featureDevCodeAcceptanceEvent: conversationId: ${event.conversationId} charactersOfCodeAccepted: ${event.charactersOfCodeAccepted} linesOfCodeAccepted: ${event.linesOfCodeAccepted}`
- )
- await this.sendFeatureDevEvent('featureDevCodeAcceptanceEvent', event)
- }
-
- public async sendMetricData(event: MetricData) {
- getLogger().debug(`featureDevCodeGenerationMetricData: dimensions: ${event.dimensions}`)
- await this.sendFeatureDevEvent('metricData', event)
- }
-
- public async sendFeatureDevEvent(
- eventName: T,
- event: NonNullable
- ) {
- try {
- const client = await this.getClient()
- const params: FeatureDevProxyClient.SendTelemetryEventRequest = {
- telemetryEvent: {
- [eventName]: event,
- },
- optOutPreference: getOptOutPreference(),
- userContext: {
- ideCategory: 'VSCODE',
- operatingSystem: getOperatingSystem(),
- product: 'FeatureDev', // Should be the same as in JetBrains
- clientId: getClientId(globals.globalState),
- ideVersion: extensionVersion,
- },
- profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn,
- }
- const response = await client.sendTelemetryEvent(params).promise()
- getLogger().debug(
- `${featureName}: successfully sent ${eventName} telemetryEvent:${'conversationId' in event ? ' ConversationId: ' + event.conversationId : ''} RequestId: ${response.$response.requestId}`
- )
- } catch (e) {
- getLogger().error(
- `${featureName}: failed to send ${eventName} telemetry: ${(e as Error).name}: ${
- (e as Error).message
- } RequestId: ${(e as any).requestId}`
- )
- }
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/constants.ts b/packages/core/src/amazonqFeatureDev/constants.ts
deleted file mode 100644
index 78cae972cc3..00000000000
--- a/packages/core/src/amazonqFeatureDev/constants.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { CodeReference } from '../amazonq/webview/ui/connector'
-import { LicenseUtil } from '../codewhisperer/util/licenseUtil'
-
-// The Scheme name of the virtual documents.
-export const featureDevScheme = 'aws-featureDev'
-
-// For uniquely identifiying which chat messages should be routed to FeatureDev
-export const featureDevChat = 'featureDevChat'
-
-export const featureName = 'Amazon Q Developer Agent for software development'
-
-export const generateDevFilePrompt =
- "generate a devfile in my repository. Note that you should only use devfile version 2.0.0 and the only supported commands are install, build and test (are all optional). so you may have to bundle some commands together using '&&'. also you can use ”public.ecr.aws/aws-mde/universal-image:latest” as universal image if you aren’t sure which image to use. here is an example for a node repository (but don't assume it's always a node project. look at the existing repository structure before generating the devfile): schemaVersion: 2.0.0 components: - name: dev container: image: public.ecr.aws/aws-mde/universal-image:latest commands: - id: install exec: component: dev commandLine: ”npm install” - id: build exec: component: dev commandLine: ”npm run build” - id: test exec: component: dev commandLine: ”npm run test”"
-
-// Max allowed size for file collection
-export const maxRepoSizeBytes = 200 * 1024 * 1024
-
-export const startCodeGenClientErrorMessages = ['Improperly formed request', 'Resource not found']
-export const startTaskAssistLimitReachedMessage = 'StartTaskAssistCodeGeneration reached for this month.'
-export const clientErrorMessages = [
- 'The folder you chose did not contain any source files in a supported language. Choose another folder and try again.',
-]
-
-// License text that's used in the file view
-export const licenseText = (reference: CodeReference) =>
- `${
- reference.licenseName
- } license from repository ${reference.repository}`
diff --git a/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts b/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts
deleted file mode 100644
index bdf73eada07..00000000000
--- a/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts
+++ /dev/null
@@ -1,1089 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatItemAction, MynahIcons } from '@aws/mynah-ui'
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { EventEmitter } from 'vscode'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { createSingleFileDialog } from '../../../shared/ui/common/openDialog'
-import {
- CodeIterationLimitError,
- ContentLengthError,
- createUserFacingErrorMessage,
- denyListedErrors,
- FeatureDevServiceError,
- getMetricResult,
- MonthlyConversationLimitError,
- NoChangeRequiredException,
- PrepareRepoFailedError,
- PromptRefusalException,
- SelectedFolderNotInWorkspaceFolderError,
- TabIdNotFoundError,
- UploadCodeError,
- UploadURLExpired,
- UserMessageNotFoundError,
- WorkspaceFolderNotFoundError,
- ZipFileError,
-} from '../../errors'
-import { codeGenRetryLimit, defaultRetryLimit } from '../../limits'
-import { Session } from '../../session/session'
-import { featureDevScheme, featureName, generateDevFilePrompt } from '../../constants'
-import {
- DeletedFileInfo,
- DevPhase,
- MetricDataOperationName,
- MetricDataResult,
- type NewFileInfo,
-} from '../../../amazonq/commons/types'
-import { AuthUtil } from '../../../codewhisperer/util/authUtil'
-import { AuthController } from '../../../amazonq/auth/controller'
-import { getLogger } from '../../../shared/logger/logger'
-import { submitFeedback } from '../../../feedback/vue/submitFeedback'
-import { Commands, placeholder } from '../../../shared/vscode/commands2'
-import { EditorContentController } from '../../../amazonq/commons/controllers/contentController'
-import { openUrl } from '../../../shared/utilities/vsCodeUtils'
-import { checkForDevFile, getPathsFromZipFilePath } from '../../../amazonq/util/files'
-import { examples, messageWithConversationId } from '../../userFacingText'
-import { getWorkspaceFoldersByPrefixes } from '../../../shared/utilities/workspaceUtils'
-import { openDeletedDiff, openDiff } from '../../../amazonq/commons/diff'
-import { i18n } from '../../../shared/i18n-helper'
-import globals from '../../../shared/extensionGlobals'
-import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings'
-import { randomUUID } from '../../../shared/crypto'
-import { FollowUpTypes } from '../../../amazonq/commons/types'
-import { Messenger } from '../../../amazonq/commons/connector/baseMessenger'
-import { BaseChatSessionStorage } from '../../../amazonq/commons/baseChatStorage'
-
-export interface ChatControllerEventEmitters {
- readonly processHumanChatMessage: EventEmitter
- readonly followUpClicked: EventEmitter
- readonly openDiff: EventEmitter
- readonly stopResponse: EventEmitter
- readonly tabOpened: EventEmitter
- readonly tabClosed: EventEmitter
- readonly processChatItemVotedMessage: EventEmitter
- readonly processChatItemFeedbackMessage: EventEmitter
- readonly authClicked: EventEmitter
- readonly processResponseBodyLinkClick: EventEmitter
- readonly insertCodeAtPositionClicked: EventEmitter
- readonly fileClicked: EventEmitter
- readonly storeCodeResultMessageId: EventEmitter
-}
-
-type OpenDiffMessage = {
- tabID: string
- messageId: string
- // currently the zip file path
- filePath: string
- deleted: boolean
- codeGenerationId: string
-}
-
-type fileClickedMessage = {
- tabID: string
- messageId: string
- filePath: string
- actionName: string
-}
-
-type StoreMessageIdMessage = {
- tabID: string
- messageId: string
-}
-
-export class FeatureDevController {
- private readonly scheme: string = featureDevScheme
- private readonly messenger: Messenger
- private readonly sessionStorage: BaseChatSessionStorage
- private isAmazonQVisible: boolean
- private authController: AuthController
- private contentController: EditorContentController
-
- public constructor(
- private readonly chatControllerMessageListeners: ChatControllerEventEmitters,
- messenger: Messenger,
- sessionStorage: BaseChatSessionStorage,
- onDidChangeAmazonQVisibility: vscode.Event
- ) {
- this.messenger = messenger
- this.sessionStorage = sessionStorage
- this.authController = new AuthController()
- this.contentController = new EditorContentController()
-
- /**
- * defaulted to true because onDidChangeAmazonQVisibility doesn't get fire'd until after
- * the view is opened
- */
- this.isAmazonQVisible = true
-
- onDidChangeAmazonQVisibility((visible) => {
- this.isAmazonQVisible = visible
- })
-
- this.chatControllerMessageListeners.processHumanChatMessage.event((data) => {
- this.processUserChatMessage(data).catch((e) => {
- getLogger().error('processUserChatMessage failed: %s', (e as Error).message)
- })
- })
- this.chatControllerMessageListeners.processChatItemVotedMessage.event((data) => {
- this.processChatItemVotedMessage(data.tabID, data.vote).catch((e) => {
- getLogger().error('processChatItemVotedMessage failed: %s', (e as Error).message)
- })
- })
- this.chatControllerMessageListeners.processChatItemFeedbackMessage.event((data) => {
- this.processChatItemFeedbackMessage(data).catch((e) => {
- getLogger().error('processChatItemFeedbackMessage failed: %s', (e as Error).message)
- })
- })
- this.chatControllerMessageListeners.followUpClicked.event((data) => {
- switch (data.followUp.type) {
- case FollowUpTypes.InsertCode:
- return this.insertCode(data)
- case FollowUpTypes.ProvideFeedbackAndRegenerateCode:
- return this.provideFeedbackAndRegenerateCode(data)
- case FollowUpTypes.Retry:
- return this.retryRequest(data)
- case FollowUpTypes.ModifyDefaultSourceFolder:
- return this.modifyDefaultSourceFolder(data)
- case FollowUpTypes.DevExamples:
- this.initialExamples(data)
- break
- case FollowUpTypes.NewTask:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: data?.tabID,
- message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'),
- })
- return this.newTask(data)
- case FollowUpTypes.CloseSession:
- return this.closeSession(data)
- case FollowUpTypes.SendFeedback:
- this.sendFeedback()
- break
- case FollowUpTypes.AcceptAutoBuild:
- return this.processAutoBuildSetting(true, data)
- case FollowUpTypes.DenyAutoBuild:
- return this.processAutoBuildSetting(false, data)
- case FollowUpTypes.GenerateDevFile:
- this.messenger.sendAnswer({
- type: 'system-prompt',
- tabID: data?.tabID,
- message: i18n('AWS.amazonq.featureDev.pillText.generateDevFile'),
- })
- return this.newTask(data, generateDevFilePrompt)
- }
- })
- this.chatControllerMessageListeners.openDiff.event((data) => {
- return this.openDiff(data)
- })
- this.chatControllerMessageListeners.stopResponse.event((data) => {
- return this.stopResponse(data)
- })
- this.chatControllerMessageListeners.tabOpened.event((data) => {
- return this.tabOpened(data)
- })
- this.chatControllerMessageListeners.tabClosed.event((data) => {
- this.tabClosed(data)
- })
- this.chatControllerMessageListeners.authClicked.event((data) => {
- this.authClicked(data)
- })
- this.chatControllerMessageListeners.processResponseBodyLinkClick.event((data) => {
- this.processLink(data)
- })
- this.chatControllerMessageListeners.insertCodeAtPositionClicked.event((data) => {
- this.insertCodeAtPosition(data)
- })
- this.chatControllerMessageListeners.fileClicked.event(async (data) => {
- return await this.fileClicked(data)
- })
- this.chatControllerMessageListeners.storeCodeResultMessageId.event(async (data) => {
- return await this.storeCodeResultMessageId(data)
- })
- AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => {
- this.sessionStorage.deleteAllSessions()
- })
- }
-
- private async processChatItemVotedMessage(tabId: string, vote: string) {
- const session = await this.sessionStorage.getSession(tabId)
-
- if (vote === 'upvote') {
- telemetry.amazonq_codeGenerationThumbsUp.emit({
- amazonqConversationId: session?.conversationId,
- value: 1,
- result: 'Succeeded',
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
- } else if (vote === 'downvote') {
- telemetry.amazonq_codeGenerationThumbsDown.emit({
- amazonqConversationId: session?.conversationId,
- value: 1,
- result: 'Succeeded',
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
- }
- }
-
- private async processChatItemFeedbackMessage(message: any) {
- const session = await this.sessionStorage.getSession(message.tabId)
-
- await globals.telemetry.postFeedback({
- comment: `${JSON.stringify({
- type: 'featuredev-chat-answer-feedback',
- conversationId: session?.conversationId ?? '',
- messageId: message?.messageId,
- reason: message?.selectedOption,
- userComment: message?.comment,
- })}`,
- sentiment: 'Negative', // The chat UI reports only negative feedback currently.
- })
- }
-
- private processErrorChatMessage = (err: any, message: any, session: Session | undefined) => {
- const errorMessage = createUserFacingErrorMessage(
- `${featureName} request failed: ${err.cause?.message ?? err.message}`
- )
-
- let defaultMessage
- const isDenyListedError = denyListedErrors.some((denyListedError) => err.message.includes(denyListedError))
-
- switch (err.constructor.name) {
- case ContentLengthError.name:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
- canBeVoted: true,
- })
- this.messenger.sendAnswer({
- type: 'system-prompt',
- tabID: message.tabID,
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.modifyDefaultSourceFolder'),
- type: 'ModifyDefaultSourceFolder',
- status: 'info',
- },
- ],
- })
- break
- case MonthlyConversationLimitError.name:
- this.messenger.sendMonthlyLimitError(message.tabID)
- break
- case FeatureDevServiceError.name:
- case UploadCodeError.name:
- case UserMessageNotFoundError.name:
- case TabIdNotFoundError.name:
- case PrepareRepoFailedError.name:
- this.messenger.sendErrorMessage(
- errorMessage,
- message.tabID,
- this.retriesRemaining(session),
- session?.conversationIdUnsafe
- )
- break
- case PromptRefusalException.name:
- case ZipFileError.name:
- this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, true)
- break
- case NoChangeRequiredException.name:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message,
- canBeVoted: true,
- })
- // Allow users to re-work the task description.
- return this.newTask(message)
- case CodeIterationLimitError.name:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
- canBeVoted: true,
- })
- this.messenger.sendAnswer({
- type: 'system-prompt',
- tabID: message.tabID,
- followUps: [
- {
- pillText:
- session?.getInsertCodePillText([
- ...(session?.state.filePaths ?? []),
- ...(session?.state.deletedFiles ?? []),
- ]) ?? i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'),
- type: FollowUpTypes.InsertCode,
- icon: 'ok' as MynahIcons,
- status: 'success',
- },
- ],
- })
- break
- case UploadURLExpired.name:
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message,
- canBeVoted: true,
- })
- break
- default:
- if (isDenyListedError || this.retriesRemaining(session) === 0) {
- defaultMessage = i18n('AWS.amazonq.featureDev.error.codeGen.denyListedError')
- } else {
- defaultMessage = i18n('AWS.amazonq.featureDev.error.codeGen.default')
- }
-
- this.messenger.sendErrorMessage(
- defaultMessage ? defaultMessage : errorMessage,
- message.tabID,
- this.retriesRemaining(session),
- session?.conversationIdUnsafe,
- !!defaultMessage
- )
-
- break
- }
- }
-
- /**
- *
- * This function dispose cancellation token to free resources and provide a new token.
- * Since user can abort a call in the same session, when the processing ends, we need provide a new one
- * to start with the new prompt and allow the ability to stop again.
- *
- * @param session
- */
-
- private disposeToken(session: Session | undefined) {
- if (session?.state?.tokenSource?.token.isCancellationRequested) {
- session?.state.tokenSource?.dispose()
- if (session?.state?.tokenSource) {
- session.state.tokenSource = new vscode.CancellationTokenSource()
- }
- getLogger().debug('Request cancelled, skipping further processing')
- }
- }
-
- // TODO add type
- private async processUserChatMessage(message: any) {
- if (message.message === undefined) {
- this.messenger.sendErrorMessage('chatMessage should be set', message.tabID, 0, undefined)
- return
- }
-
- /**
- * Don't attempt to process any chat messages when a workspace folder is not set.
- * When the tab is first opened we will throw an error and lock the chat if the workspace
- * folder is not found
- */
- const workspaceFolders = vscode.workspace.workspaceFolders
- if (workspaceFolders === undefined || workspaceFolders.length === 0) {
- return
- }
-
- let session
- try {
- getLogger().debug(`${featureName}: Processing message: ${message.message}`)
-
- session = await this.sessionStorage.getSession(message.tabID)
- // set latestMessage in session as retry would lose context if function returns early
- session.latestMessage = message.message
-
- await session.disableFileList()
- const authState = await AuthUtil.instance.getChatAuthState()
- if (authState.amazonQ !== 'connected') {
- await this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
- session.isAuthenticating = true
- return
- }
-
- const root = session.getWorkspaceRoot()
- const autoBuildProjectSetting = CodeWhispererSettings.instance.getAutoBuildSetting()
- const hasDevfile = await checkForDevFile(root)
- const isPromptedForAutoBuildFeature = Object.keys(autoBuildProjectSetting).includes(root)
-
- if (hasDevfile && !isPromptedForAutoBuildFeature) {
- await this.promptAllowQCommandsConsent(message.tabID)
- return
- }
-
- await session.preloader()
-
- if (session.state.phase === DevPhase.CODEGEN) {
- await this.onCodeGeneration(session, message.message, message.tabID)
- }
- } catch (err: any) {
- this.disposeToken(session)
- await this.processErrorChatMessage(err, message, session)
- // Lock the chat input until they explicitly click one of the follow ups
- this.messenger.sendChatInputEnabled(message.tabID, false)
- }
- }
-
- private async promptAllowQCommandsConsent(tabID: string) {
- this.messenger.sendAnswer({
- tabID: tabID,
- message: i18n('AWS.amazonq.featureDev.answer.devFileInRepository'),
- type: 'answer',
- })
-
- this.messenger.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.acceptForProject'),
- type: FollowUpTypes.AcceptAutoBuild,
- status: 'success',
- },
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.declineForProject'),
- type: FollowUpTypes.DenyAutoBuild,
- status: 'error',
- },
- ],
- tabID: tabID,
- })
- }
-
- /**
- * Handle a regular incoming message when a user is in the code generation phase
- */
- private async onCodeGeneration(session: Session, message: string, tabID: string) {
- // lock the UI/show loading bubbles
- this.messenger.sendAsyncEventProgress(
- tabID,
- true,
- session.retries === codeGenRetryLimit
- ? i18n('AWS.amazonq.featureDev.pillText.awaitMessage')
- : i18n('AWS.amazonq.featureDev.pillText.awaitMessageRetry')
- )
-
- try {
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.requestingChanges'),
- type: 'answer-stream',
- tabID,
- canBeVoted: true,
- })
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.generatingCode'))
- await session.sendMetricDataTelemetry(MetricDataOperationName.StartCodeGeneration, MetricDataResult.Success)
- await session.send(message)
- const filePaths = session.state.filePaths ?? []
- const deletedFiles = session.state.deletedFiles ?? []
- // Only add the follow up accept/deny buttons when the tab hasn't been closed/request hasn't been cancelled
- if (session?.state?.tokenSource?.token.isCancellationRequested) {
- return
- }
-
- if (filePaths.length === 0 && deletedFiles.length === 0) {
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.unableGenerateChanges'),
- type: 'answer',
- tabID: tabID,
- canBeVoted: true,
- })
- this.messenger.sendAnswer({
- type: 'system-prompt',
- tabID: tabID,
- followUps:
- this.retriesRemaining(session) > 0
- ? [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.retry'),
- type: FollowUpTypes.Retry,
- status: 'warning',
- },
- ]
- : [],
- })
- // Lock the chat input until they explicitly click retry
- this.messenger.sendChatInputEnabled(tabID, false)
- return
- }
-
- this.messenger.sendCodeResult(
- filePaths,
- deletedFiles,
- session.state.references ?? [],
- tabID,
- session.uploadId,
- session.state.codeGenerationId ?? ''
- )
-
- const remainingIterations = session.state.codeGenerationRemainingIterationCount
- const totalIterations = session.state.codeGenerationTotalIterationCount
-
- if (remainingIterations !== undefined && totalIterations !== undefined) {
- this.messenger.sendAnswer({
- type: 'answer' as const,
- tabID: tabID,
- message: (() => {
- if (remainingIterations > 2) {
- return 'Would you like me to add this code to your project, or provide feedback for new code?'
- } else if (remainingIterations > 0) {
- return `Would you like me to add this code to your project, or provide feedback for new code? You have ${remainingIterations} out of ${totalIterations} code generations left.`
- } else {
- return 'Would you like me to add this code to your project?'
- }
- })(),
- })
- }
-
- if (session?.state.phase === DevPhase.CODEGEN) {
- const messageId = randomUUID()
- session.updateAcceptCodeMessageId(messageId)
- session.updateAcceptCodeTelemetrySent(false)
- // need to add the followUps with an extra update here, or it will double-render them
- this.messenger.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [],
- tabID: tabID,
- messageId,
- })
- await session.updateChatAnswer(tabID, i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'))
- await session.sendLinesOfCodeGeneratedTelemetry()
- }
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption'))
- } catch (err: any) {
- getLogger().error(`${featureName}: Error during code generation: ${err}`)
- await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, getMetricResult(err))
- throw err
- } finally {
- // Finish processing the event
-
- if (session?.state?.tokenSource?.token.isCancellationRequested) {
- await this.workOnNewTask(
- session.tabID,
- session.state.codeGenerationRemainingIterationCount,
- session.state.codeGenerationTotalIterationCount,
- session?.state?.tokenSource?.token.isCancellationRequested
- )
- this.disposeToken(session)
- } else {
- this.messenger.sendAsyncEventProgress(tabID, false, undefined)
-
- // Lock the chat input until they explicitly click one of the follow ups
- this.messenger.sendChatInputEnabled(tabID, false)
-
- if (!this.isAmazonQVisible) {
- const open = 'Open chat'
- const resp = await vscode.window.showInformationMessage(
- i18n('AWS.amazonq.featureDev.answer.qGeneratedCode'),
- open
- )
- if (resp === open) {
- await Commands.tryExecute('aws.amazonq.AmazonQChatView.focus')
- // TODO add focusing on the specific tab once that's implemented
- }
- }
- }
- }
- await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, MetricDataResult.Success)
- }
-
- private sendUpdateCodeMessage(tabID: string) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID,
- message: i18n('AWS.amazonq.featureDev.answer.updateCode'),
- canBeVoted: true,
- })
- }
-
- private async workOnNewTask(
- tabID: string,
- remainingIterations: number = 0,
- totalIterations?: number,
- isStoppedGeneration: boolean = false
- ) {
- const hasDevFile = await checkForDevFile((await this.sessionStorage.getSession(tabID)).getWorkspaceRoot())
-
- if (isStoppedGeneration) {
- this.messenger.sendAnswer({
- message: ((remainingIterations) => {
- if (totalIterations !== undefined) {
- if (remainingIterations <= 0) {
- return "I stopped generating your code. You don't have more iterations left, however, you can start a new session."
- } else if (remainingIterations <= 2) {
- return `I stopped generating your code. If you want to continue working on this task, provide another description. You have ${remainingIterations} out of ${totalIterations} code generations left.`
- }
- }
- return 'I stopped generating your code. If you want to continue working on this task, provide another description.'
- })(remainingIterations),
- type: 'answer-part',
- tabID,
- })
- }
-
- if ((remainingIterations <= 0 && isStoppedGeneration) || !isStoppedGeneration) {
- const followUps: Array = [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.newTask'),
- type: FollowUpTypes.NewTask,
- status: 'info',
- },
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.closeSession'),
- type: FollowUpTypes.CloseSession,
- status: 'info',
- },
- ]
-
- if (!hasDevFile) {
- followUps.push({
- pillText: i18n('AWS.amazonq.featureDev.pillText.generateDevFile'),
- type: FollowUpTypes.GenerateDevFile,
- status: 'info',
- })
-
- this.messenger.sendAnswer({
- type: 'answer',
- tabID,
- message: i18n('AWS.amazonq.featureDev.answer.devFileSuggestion'),
- })
- }
-
- this.messenger.sendAnswer({
- type: 'system-prompt',
- tabID,
- followUps,
- })
- this.messenger.sendChatInputEnabled(tabID, false)
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption'))
- return
- }
-
- // Ensure that chat input is enabled so that they can provide additional iterations if they choose
- this.messenger.sendChatInputEnabled(tabID, true)
- this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.placeholder.additionalImprovements'))
- }
-
- private async processAutoBuildSetting(setting: boolean, msg: any) {
- const root = (await this.sessionStorage.getSession(msg.tabID)).getWorkspaceRoot()
- await CodeWhispererSettings.instance.updateAutoBuildSetting(root, setting)
-
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.answer.settingUpdated'),
- tabID: msg.tabID,
- type: 'answer',
- })
-
- await this.retryRequest(msg)
- }
-
- // TODO add type
- private async insertCode(message: any) {
- let session
- try {
- session = await this.sessionStorage.getSession(message.tabID)
-
- const acceptedFiles = (paths?: { rejected: boolean }[]) => (paths || []).filter((i) => !i.rejected).length
-
- const filesAccepted = acceptedFiles(session.state.filePaths) + acceptedFiles(session.state.deletedFiles)
-
- this.sendAcceptCodeTelemetry(session, filesAccepted)
-
- await session.insertChanges()
-
- if (session.acceptCodeMessageId) {
- this.sendUpdateCodeMessage(message.tabID)
- await this.workOnNewTask(
- message.tabID,
- session.state.codeGenerationRemainingIterationCount,
- session.state.codeGenerationTotalIterationCount
- )
- await this.clearAcceptCodeMessageId(message.tabID)
- }
- } catch (err: any) {
- this.messenger.sendErrorMessage(
- createUserFacingErrorMessage(`Failed to insert code changes: ${err.message}`),
- message.tabID,
- this.retriesRemaining(session),
- session?.conversationIdUnsafe
- )
- }
- }
-
- private async provideFeedbackAndRegenerateCode(message: any) {
- const session = await this.sessionStorage.getSession(message.tabID)
- telemetry.amazonq_isProvideFeedbackForCodeGen.emit({
- amazonqConversationId: session.conversationId,
- enabled: true,
- result: 'Succeeded',
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
- // Unblock the message button
- this.messenger.sendAsyncEventProgress(message.tabID, false, undefined)
-
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: i18n('AWS.amazonq.featureDev.answer.howCodeCanBeImproved'),
- canBeVoted: true,
- })
-
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.feedback'))
- }
-
- private async retryRequest(message: any) {
- let session
- try {
- this.messenger.sendAsyncEventProgress(message.tabID, true, undefined)
-
- session = await this.sessionStorage.getSession(message.tabID)
-
- // Decrease retries before making this request, just in case this one fails as well
- session.decreaseRetries()
-
- // Sending an empty message will re-run the last state with the previous values
- await this.processUserChatMessage({
- message: session.latestMessage,
- tabID: message.tabID,
- })
- } catch (err: any) {
- this.messenger.sendErrorMessage(
- createUserFacingErrorMessage(`Failed to retry request: ${err.message}`),
- message.tabID,
- this.retriesRemaining(session),
- session?.conversationIdUnsafe
- )
- } finally {
- // Finish processing the event
- this.messenger.sendAsyncEventProgress(message.tabID, false, undefined)
- }
- }
-
- private async modifyDefaultSourceFolder(message: any) {
- const session = await this.sessionStorage.getSession(message.tabID)
-
- const uri = await createSingleFileDialog({
- canSelectFolders: true,
- canSelectFiles: false,
- }).prompt()
-
- let metricData: { result: 'Succeeded' } | { result: 'Failed'; reason: string } | undefined
-
- if (!(uri instanceof vscode.Uri)) {
- this.messenger.sendAnswer({
- tabID: message.tabID,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.selectFiles'),
- type: 'ModifyDefaultSourceFolder',
- status: 'info',
- },
- ],
- })
- metricData = { result: 'Failed', reason: 'ClosedBeforeSelection' }
- } else if (!vscode.workspace.getWorkspaceFolder(uri)) {
- this.messenger.sendAnswer({
- tabID: message.tabID,
- type: 'answer',
- message: new SelectedFolderNotInWorkspaceFolderError().message,
- canBeVoted: true,
- })
- this.messenger.sendAnswer({
- tabID: message.tabID,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.selectFiles'),
- type: 'ModifyDefaultSourceFolder',
- status: 'info',
- },
- ],
- })
- metricData = { result: 'Failed', reason: 'NotInWorkspaceFolder' }
- } else {
- session.updateWorkspaceRoot(uri.fsPath)
- metricData = { result: 'Succeeded' }
- this.messenger.sendAnswer({
- message: `Changed source root to: ${uri.fsPath}`,
- type: 'answer',
- tabID: message.tabID,
- canBeVoted: true,
- })
- this.messenger.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.retry'),
- type: FollowUpTypes.Retry,
- status: 'warning',
- },
- ],
- tabID: message.tabID,
- })
- this.messenger.sendChatInputEnabled(message.tabID, true)
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.pillText.writeNewPrompt'))
- }
-
- telemetry.amazonq_modifySourceFolder.emit({
- credentialStartUrl: AuthUtil.instance.startUrl,
- amazonqConversationId: session.conversationId,
- ...metricData,
- })
- }
-
- private initialExamples(message: any) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: examples,
- canBeVoted: true,
- })
- }
-
- private async fileClicked(message: fileClickedMessage) {
- // TODO: add Telemetry here
- const tabId: string = message.tabID
- const messageId = message.messageId
- const filePathToUpdate: string = message.filePath
- const action = message.actionName
-
- const session = await this.sessionStorage.getSession(tabId)
- const filePathIndex = (session.state.filePaths ?? []).findIndex((obj) => obj.relativePath === filePathToUpdate)
- const deletedFilePathIndex = (session.state.deletedFiles ?? []).findIndex(
- (obj) => obj.relativePath === filePathToUpdate
- )
-
- if (filePathIndex !== -1 && session.state.filePaths) {
- if (action === 'accept-change') {
- this.sendAcceptCodeTelemetry(session, 1)
- await session.insertNewFiles([session.state.filePaths[filePathIndex]])
- await session.insertCodeReferenceLogs(session.state.references ?? [])
- await this.openFile(session.state.filePaths[filePathIndex], tabId)
- } else {
- session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected
- }
- }
- if (deletedFilePathIndex !== -1 && session.state.deletedFiles) {
- if (action === 'accept-change') {
- this.sendAcceptCodeTelemetry(session, 1)
- await session.applyDeleteFiles([session.state.deletedFiles[deletedFilePathIndex]])
- await session.insertCodeReferenceLogs(session.state.references ?? [])
- } else {
- session.state.deletedFiles[deletedFilePathIndex].rejected =
- !session.state.deletedFiles[deletedFilePathIndex].rejected
- }
- }
-
- await session.updateFilesPaths({
- tabID: tabId,
- filePaths: session.state.filePaths ?? [],
- deletedFiles: session.state.deletedFiles ?? [],
- messageId,
- })
-
- if (session.acceptCodeMessageId) {
- const allFilePathsAccepted = session.state.filePaths?.every(
- (filePath: NewFileInfo) => !filePath.rejected && filePath.changeApplied
- )
- const allDeletedFilePathsAccepted = session.state.deletedFiles?.every(
- (filePath: DeletedFileInfo) => !filePath.rejected && filePath.changeApplied
- )
- if (allFilePathsAccepted && allDeletedFilePathsAccepted) {
- this.sendUpdateCodeMessage(tabId)
- await this.workOnNewTask(
- tabId,
- session.state.codeGenerationRemainingIterationCount,
- session.state.codeGenerationTotalIterationCount
- )
- await this.clearAcceptCodeMessageId(tabId)
- }
- }
- }
-
- private async storeCodeResultMessageId(message: StoreMessageIdMessage) {
- const tabId: string = message.tabID
- const messageId = message.messageId
- const session = await this.sessionStorage.getSession(tabId)
-
- session.updateCodeResultMessageId(messageId)
- }
-
- private async openDiff(message: OpenDiffMessage) {
- const tabId: string = message.tabID
- const codeGenerationId: string = message.messageId
- const zipFilePath: string = message.filePath
- const session = await this.sessionStorage.getSession(tabId)
- telemetry.amazonq_isReviewedChanges.emit({
- amazonqConversationId: session.conversationId,
- enabled: true,
- result: 'Succeeded',
- credentialStartUrl: AuthUtil.instance.startUrl,
- })
-
- const workspacePrefixMapping = getWorkspaceFoldersByPrefixes(session.config.workspaceFolders)
- const pathInfos = getPathsFromZipFilePath(zipFilePath, workspacePrefixMapping, session.config.workspaceFolders)
-
- if (message.deleted) {
- const name = path.basename(pathInfos.relativePath)
- await openDeletedDiff(pathInfos.absolutePath, name, tabId, this.scheme)
- } else {
- let uploadId = session.uploadId
- if (session?.state?.uploadHistory && session.state.uploadHistory[codeGenerationId]) {
- uploadId = session?.state?.uploadHistory[codeGenerationId].uploadId
- }
- const rightPath = path.join(uploadId, zipFilePath)
- await openDiff(pathInfos.absolutePath, rightPath, tabId, this.scheme)
- }
- }
-
- private async openFile(filePath: NewFileInfo, tabId: string) {
- const leftPath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
- const rightPath = filePath.virtualMemoryUri.path
- await openDiff(leftPath, rightPath, tabId, this.scheme)
- }
-
- private async stopResponse(message: any) {
- telemetry.ui_click.emit({ elementId: 'amazonq_stopCodeGeneration' })
- this.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration'),
- type: 'answer-part',
- tabID: message.tabID,
- })
- this.messenger.sendUpdatePlaceholder(
- message.tabID,
- i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration')
- )
- this.messenger.sendChatInputEnabled(message.tabID, false)
-
- const session = await this.sessionStorage.getSession(message.tabID)
- if (session.state?.tokenSource) {
- session.state?.tokenSource?.cancel()
- }
- }
-
- private async tabOpened(message: any) {
- let session: Session | undefined
- try {
- session = await this.sessionStorage.getSession(message.tabID)
- getLogger().debug(`${featureName}: Session created with id: ${session.tabID}`)
-
- const authState = await AuthUtil.instance.getChatAuthState()
- if (authState.amazonQ !== 'connected') {
- void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID)
- session.isAuthenticating = true
- return
- }
- } catch (err: any) {
- if (err instanceof WorkspaceFolderNotFoundError) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: err.message,
- })
- this.messenger.sendChatInputEnabled(message.tabID, false)
- } else {
- this.messenger.sendErrorMessage(
- createUserFacingErrorMessage(err.message),
- message.tabID,
- this.retriesRemaining(session),
- session?.conversationIdUnsafe
- )
- }
- }
- }
-
- private authClicked(message: any) {
- this.authController.handleAuth(message.authType)
-
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: i18n('AWS.amazonq.featureDev.pillText.reauthenticate'),
- })
-
- // Explicitly ensure the user goes through the re-authenticate flow
- this.messenger.sendChatInputEnabled(message.tabID, false)
- }
-
- private tabClosed(message: any) {
- this.sessionStorage.deleteSession(message.tabID)
- }
-
- private async newTask(message: any, prefilledPrompt?: string) {
- // Old session for the tab is ending, delete it so we can create a new one for the message id
- const session = await this.sessionStorage.getSession(message.tabID)
- await session.disableFileList()
- telemetry.amazonq_endChat.emit({
- amazonqConversationId: session.conversationId,
- amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime,
- result: 'Succeeded',
- })
- this.sessionStorage.deleteSession(message.tabID)
-
- // Re-run the opening flow, where we check auth + create a session
- await this.tabOpened(message)
-
- if (prefilledPrompt) {
- await this.processUserChatMessage({ ...message, message: prefilledPrompt })
- } else {
- this.messenger.sendChatInputEnabled(message.tabID, true)
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.describe'))
- }
- }
-
- private async closeSession(message: any) {
- this.messenger.sendAnswer({
- type: 'answer',
- tabID: message.tabID,
- message: i18n('AWS.amazonq.featureDev.answer.sessionClosed'),
- })
- this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.sessionClosed'))
- this.messenger.sendChatInputEnabled(message.tabID, false)
-
- const session = await this.sessionStorage.getSession(message.tabID)
- await session.disableFileList()
- telemetry.amazonq_endChat.emit({
- amazonqConversationId: session.conversationId,
- amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime,
- result: 'Succeeded',
- })
- }
-
- private sendFeedback() {
- void submitFeedback(placeholder, 'Amazon Q')
- }
-
- private processLink(message: any) {
- void openUrl(vscode.Uri.parse(message.link))
- }
-
- private insertCodeAtPosition(message: any) {
- this.contentController.insertTextAtCursorPosition(message.code, () => {})
- }
-
- private retriesRemaining(session: Session | undefined) {
- return session?.retries ?? defaultRetryLimit
- }
-
- private async clearAcceptCodeMessageId(tabID: string) {
- const session = await this.sessionStorage.getSession(tabID)
- session.updateAcceptCodeMessageId(undefined)
- }
-
- private sendAcceptCodeTelemetry(session: Session, amazonqNumberOfFilesAccepted: number) {
- // accepted code telemetry is only to be sent once per iteration of code generation
- if (amazonqNumberOfFilesAccepted > 0 && !session.acceptCodeTelemetrySent) {
- session.updateAcceptCodeTelemetrySent(true)
- telemetry.amazonq_isAcceptedCodeChanges.emit({
- credentialStartUrl: AuthUtil.instance.startUrl,
- amazonqConversationId: session.conversationId,
- amazonqNumberOfFilesAccepted,
- enabled: true,
- result: 'Succeeded',
- })
- }
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/controllers/chat/messenger/constants.ts b/packages/core/src/amazonqFeatureDev/controllers/chat/messenger/constants.ts
deleted file mode 100644
index 086096b68a2..00000000000
--- a/packages/core/src/amazonqFeatureDev/controllers/chat/messenger/constants.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export type MessengerTypes = 'answer' | 'answer-part' | 'answer-stream' | 'system-prompt'
diff --git a/packages/core/src/amazonqFeatureDev/errors.ts b/packages/core/src/amazonqFeatureDev/errors.ts
deleted file mode 100644
index 2eb142f765b..00000000000
--- a/packages/core/src/amazonqFeatureDev/errors.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { featureName, clientErrorMessages, startTaskAssistLimitReachedMessage } from './constants'
-import { uploadCodeError } from './userFacingText'
-import { i18n } from '../shared/i18n-helper'
-import { LlmError } from '../amazonq/errors'
-import { MetricDataResult } from '../amazonq/commons/types'
-import {
- ClientError,
- ServiceError,
- ContentLengthError as CommonContentLengthError,
- ToolkitError,
-} from '../shared/errors'
-
-export class ConversationIdNotFoundError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.conversationIdNotFoundError'), {
- code: 'ConversationIdNotFound',
- })
- }
-}
-
-export class TabIdNotFoundError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), {
- code: 'TabIdNotFound',
- })
- }
-}
-
-export class WorkspaceFolderNotFoundError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), {
- code: 'WorkspaceFolderNotFound',
- })
- }
-}
-
-export class UserMessageNotFoundError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), {
- code: 'MessageNotFound',
- })
- }
-}
-
-export class SelectedFolderNotInWorkspaceFolderError extends ClientError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.selectedFolderNotInWorkspaceFolderError'), {
- code: 'SelectedFolderNotInWorkspaceFolder',
- })
- }
-}
-
-export class PromptRefusalException extends ClientError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), {
- code: 'PromptRefusalException',
- })
- }
-}
-
-export class NoChangeRequiredException extends ClientError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
- code: 'NoChangeRequiredException',
- })
- }
-}
-
-export class FeatureDevServiceError extends ServiceError {
- constructor(message: string, code: string) {
- super(message, { code })
- }
-}
-
-export class PrepareRepoFailedError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), {
- code: 'PrepareRepoFailed',
- })
- }
-}
-
-export class UploadCodeError extends ServiceError {
- constructor(statusCode: string) {
- super(uploadCodeError, { code: `UploadCode-${statusCode}` })
- }
-}
-
-export class UploadURLExpired extends ClientError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' })
- }
-}
-
-export class IllegalStateTransition extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.illegalStateTransition'), { code: 'IllegalStateTransition' })
- }
-}
-
-export class IllegalStateError extends ServiceError {
- constructor(message: string) {
- super(message, { code: 'IllegalStateTransition' })
- }
-}
-
-export class ContentLengthError extends CommonContentLengthError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name })
- }
-}
-
-export class ZipFileError extends ServiceError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name })
- }
-}
-
-export class CodeIterationLimitError extends ClientError {
- constructor() {
- super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name })
- }
-}
-
-export class MonthlyConversationLimitError extends ClientError {
- constructor(message: string) {
- super(message, { code: MonthlyConversationLimitError.name })
- }
-}
-
-export class UnknownApiError extends ServiceError {
- constructor(message: string, api: string) {
- super(message, { code: `${api}-Unknown` })
- }
-}
-
-export class ApiClientError extends ClientError {
- constructor(message: string, api: string, errorName: string, errorCode: number) {
- super(message, { code: `${api}-${errorName}-${errorCode}` })
- }
-}
-
-export class ApiServiceError extends ServiceError {
- constructor(message: string, api: string, errorName: string, errorCode: number) {
- super(message, { code: `${api}-${errorName}-${errorCode}` })
- }
-}
-
-export class ApiError {
- static of(message: string, api: string, errorName: string, errorCode: number) {
- if (errorCode >= 400 && errorCode < 500) {
- return new ApiClientError(message, api, errorName, errorCode)
- }
- return new ApiServiceError(message, api, errorName, errorCode)
- }
-}
-
-export const denyListedErrors: string[] = ['Deserialization error', 'Inaccessible host']
-
-export function createUserFacingErrorMessage(message: string) {
- if (denyListedErrors.some((err) => message.includes(err))) {
- return `${featureName} API request failed`
- }
- return message
-}
-
-function isAPIClientError(error: { code?: string; message: string }): boolean {
- return (
- clientErrorMessages.some((msg: string) => error.message.includes(msg)) ||
- error.message.includes(startTaskAssistLimitReachedMessage)
- )
-}
-
-export function getMetricResult(error: ToolkitError): MetricDataResult {
- if (error instanceof ClientError || isAPIClientError(error)) {
- return MetricDataResult.Error
- }
- if (error instanceof ServiceError) {
- return MetricDataResult.Fault
- }
- if (error instanceof LlmError) {
- return MetricDataResult.LlmFailure
- }
-
- return MetricDataResult.Fault
-}
diff --git a/packages/core/src/amazonqFeatureDev/index.ts b/packages/core/src/amazonqFeatureDev/index.ts
deleted file mode 100644
index 55114de0a06..00000000000
--- a/packages/core/src/amazonqFeatureDev/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export * from './userFacingText'
-export * from './errors'
-export * from './session/sessionState'
-export * from './constants'
-export { Session } from './session/session'
-export { FeatureDevClient } from './client/featureDev'
-export { FeatureDevChatSessionStorage } from './storages/chatSession'
-export { TelemetryHelper } from '../amazonq/util/telemetryHelper'
-export { prepareRepoData, PrepareRepoDataOptions } from '../amazonq/util/files'
-export { ChatControllerEventEmitters, FeatureDevController } from './controllers/chat/controller'
diff --git a/packages/core/src/amazonqFeatureDev/limits.ts b/packages/core/src/amazonqFeatureDev/limits.ts
deleted file mode 100644
index 04b677aaa0f..00000000000
--- a/packages/core/src/amazonqFeatureDev/limits.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Max number of times a user can attempt to retry a codegen request if it fails
-export const codeGenRetryLimit = 3
-
-// The default retry limit used when the session could not be found
-export const defaultRetryLimit = 0
-
-// The max size a file that is uploaded can be
-// 1024 KB
-export const maxFileSizeBytes = 1024000
diff --git a/packages/core/src/amazonqFeatureDev/models.ts b/packages/core/src/amazonqFeatureDev/models.ts
deleted file mode 100644
index ad37c01e477..00000000000
--- a/packages/core/src/amazonqFeatureDev/models.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export interface IManifestFile {
- pomArtifactId: string
- pomFolderName: string
- hilCapability: string
- pomGroupId: string
- sourcePomVersion: string
-}
diff --git a/packages/core/src/amazonqFeatureDev/session/session.ts b/packages/core/src/amazonqFeatureDev/session/session.ts
deleted file mode 100644
index c1fc81a4701..00000000000
--- a/packages/core/src/amazonqFeatureDev/session/session.ts
+++ /dev/null
@@ -1,412 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-
-import { ConversationNotStartedState, FeatureDevPrepareCodeGenState } from './sessionState'
-import {
- type DeletedFileInfo,
- type Interaction,
- type NewFileInfo,
- type SessionState,
- type SessionStateConfig,
- UpdateFilesPathsParams,
-} from '../../amazonq/commons/types'
-import { ContentLengthError, ConversationIdNotFoundError, IllegalStateError } from '../errors'
-import { featureDevChat, featureDevScheme } from '../constants'
-import fs from '../../shared/fs/fs'
-import { FeatureDevClient } from '../client/featureDev'
-import { codeGenRetryLimit } from '../limits'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { TelemetryHelper } from '../../amazonq/util/telemetryHelper'
-import { ReferenceLogViewProvider } from '../../codewhisperer/service/referenceLogViewProvider'
-import { AuthUtil } from '../../codewhisperer/util/authUtil'
-import { getLogger } from '../../shared/logger/logger'
-import { logWithConversationId } from '../userFacingText'
-import { CodeReference } from '../../amazonq/webview/ui/connector'
-import { MynahIcons } from '@aws/mynah-ui'
-import { i18n } from '../../shared/i18n-helper'
-import { computeDiff } from '../../amazonq/commons/diff'
-import { UpdateAnswerMessage } from '../../amazonq/commons/connector/connectorMessages'
-import { FollowUpTypes } from '../../amazonq/commons/types'
-import { SessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import { Messenger } from '../../amazonq/commons/connector/baseMessenger'
-import { ContentLengthError as CommonContentLengthError } from '../../shared/errors'
-import { referenceLogText } from '../../amazonq/commons/model'
-
-export class Session {
- private _state?: SessionState | Omit
- private task: string = ''
- private proxyClient: FeatureDevClient
- private _conversationId?: string
- private codeGenRetries: number
- private preloaderFinished = false
- private _latestMessage: string = ''
- private _telemetry: TelemetryHelper
- private _codeResultMessageId: string | undefined = undefined
- private _acceptCodeMessageId: string | undefined = undefined
- private _acceptCodeTelemetrySent = false
- private _reportedCodeChanges: Set
-
- // Used to keep track of whether or not the current session is currently authenticating/needs authenticating
- public isAuthenticating: boolean
-
- constructor(
- public readonly config: SessionConfig,
- private messenger: Messenger,
- public readonly tabID: string,
- initialState: Omit = new ConversationNotStartedState(tabID),
- proxyClient: FeatureDevClient = new FeatureDevClient()
- ) {
- this._state = initialState
- this.proxyClient = proxyClient
-
- this.codeGenRetries = codeGenRetryLimit
-
- this._telemetry = new TelemetryHelper()
- this.isAuthenticating = false
- this._reportedCodeChanges = new Set()
- }
-
- /**
- * Preload any events that have to run before a chat message can be sent
- */
- async preloader() {
- if (!this.preloaderFinished) {
- await this.setupConversation()
- this.preloaderFinished = true
- this.messenger.sendAsyncEventProgress(this.tabID, true, undefined)
- await this.proxyClient.sendFeatureDevTelemetryEvent(this.conversationId) // send the event only once per conversation.
- }
- }
-
- /**
- * setupConversation
- *
- * Starts a conversation with the backend and uploads the repo for the LLMs to be able to use it.
- */
- private async setupConversation() {
- await telemetry.amazonq_startConversationInvoke.run(async (span) => {
- this._conversationId = await this.proxyClient.createConversation()
- getLogger().info(logWithConversationId(this.conversationId))
-
- span.record({ amazonqConversationId: this._conversationId, credentialStartUrl: AuthUtil.instance.startUrl })
- })
-
- this._state = new FeatureDevPrepareCodeGenState(
- {
- ...this.getSessionStateConfig(),
- conversationId: this.conversationId,
- uploadId: '',
- currentCodeGenerationId: undefined,
- },
- [],
- [],
- [],
- this.tabID,
- 0
- )
- }
-
- updateWorkspaceRoot(workspaceRootFolder: string) {
- this.config.workspaceRoots = [workspaceRootFolder]
- this._state && this._state.updateWorkspaceRoot && this._state.updateWorkspaceRoot(workspaceRootFolder)
- }
-
- getWorkspaceRoot(): string {
- return this.config.workspaceRoots[0]
- }
-
- private getSessionStateConfig(): Omit {
- return {
- workspaceRoots: this.config.workspaceRoots,
- workspaceFolders: this.config.workspaceFolders,
- proxyClient: this.proxyClient,
- conversationId: this.conversationId,
- }
- }
-
- async send(msg: string): Promise {
- // When the task/"thing to do" hasn't been set yet, we want it to be the incoming message
- if (this.task === '' && msg) {
- this.task = msg
- }
-
- this._latestMessage = msg
-
- return this.nextInteraction(msg)
- }
-
- private async nextInteraction(msg: string) {
- try {
- const resp = await this.state.interact({
- task: this.task,
- msg,
- fs: this.config.fs,
- messenger: this.messenger,
- telemetry: this.telemetry,
- tokenSource: this.state.tokenSource,
- uploadHistory: this.state.uploadHistory,
- })
-
- if (resp.nextState) {
- if (!this.state?.tokenSource?.token.isCancellationRequested) {
- this.state?.tokenSource?.cancel()
- }
- // Move to the next state
- this._state = resp.nextState
- }
-
- return resp.interaction
- } catch (e) {
- if (e instanceof CommonContentLengthError) {
- getLogger().debug(`Content length validation failed: ${e.message}`)
- throw new ContentLengthError()
- }
- throw e
- }
- }
-
- public async updateFilesPaths(params: UpdateFilesPathsParams) {
- const { tabID, filePaths, deletedFiles, messageId, disableFileActions = false } = params
- this.messenger.updateFileComponent(tabID, filePaths, deletedFiles, messageId, disableFileActions)
- await this.updateChatAnswer(tabID, this.getInsertCodePillText([...filePaths, ...deletedFiles]))
- }
-
- public async updateChatAnswer(tabID: string, insertCodePillText: string) {
- if (this._acceptCodeMessageId) {
- const answer = new UpdateAnswerMessage(
- {
- messageId: this._acceptCodeMessageId,
- messageType: 'system-prompt',
- followUps: [
- {
- pillText: insertCodePillText,
- type: FollowUpTypes.InsertCode,
- icon: 'ok' as MynahIcons,
- status: 'success',
- },
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.provideFeedback'),
- type: FollowUpTypes.ProvideFeedbackAndRegenerateCode,
- icon: 'refresh' as MynahIcons,
- status: 'info',
- },
- ],
- },
- tabID,
- featureDevChat
- )
- this.messenger.updateChatAnswer(answer)
- }
- }
-
- public async insertChanges() {
- const newFilePaths =
- this.state.filePaths?.filter((filePath) => !filePath.rejected && !filePath.changeApplied) ?? []
- await this.insertNewFiles(newFilePaths)
-
- const deletedFiles =
- this.state.deletedFiles?.filter((deletedFile) => !deletedFile.rejected && !deletedFile.changeApplied) ?? []
- await this.applyDeleteFiles(deletedFiles)
-
- await this.insertCodeReferenceLogs(this.state.references ?? [])
-
- if (this._codeResultMessageId) {
- await this.updateFilesPaths({
- tabID: this.state.tabID,
- filePaths: this.state.filePaths ?? [],
- deletedFiles: this.state.deletedFiles ?? [],
- messageId: this._codeResultMessageId,
- })
- }
- }
-
- public async insertNewFiles(newFilePaths: NewFileInfo[]) {
- await this.sendLinesOfCodeAcceptedTelemetry(newFilePaths)
- for (const filePath of newFilePaths) {
- const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
-
- const uri = filePath.virtualMemoryUri
- const content = await this.config.fs.readFile(uri)
- const decodedContent = new TextDecoder().decode(content)
-
- await fs.mkdir(path.dirname(absolutePath))
- await fs.writeFile(absolutePath, decodedContent)
- filePath.changeApplied = true
- }
- }
-
- public async applyDeleteFiles(deletedFiles: DeletedFileInfo[]) {
- for (const filePath of deletedFiles) {
- const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath)
- await fs.delete(absolutePath)
- filePath.changeApplied = true
- }
- }
-
- public async insertCodeReferenceLogs(codeReferences: CodeReference[]) {
- for (const ref of codeReferences) {
- ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref))
- }
- }
-
- public async disableFileList() {
- if (this._codeResultMessageId === undefined) {
- return
- }
-
- await this.updateFilesPaths({
- tabID: this.state.tabID,
- filePaths: this.state.filePaths ?? [],
- deletedFiles: this.state.deletedFiles ?? [],
- messageId: this._codeResultMessageId,
- disableFileActions: true,
- })
- this._codeResultMessageId = undefined
- }
-
- public updateCodeResultMessageId(messageId?: string) {
- this._codeResultMessageId = messageId
- }
-
- public updateAcceptCodeMessageId(messageId?: string) {
- this._acceptCodeMessageId = messageId
- }
-
- public updateAcceptCodeTelemetrySent(sent: boolean) {
- this._acceptCodeTelemetrySent = sent
- }
-
- public getInsertCodePillText(files: Array) {
- if (files.every((file) => file.rejected || file.changeApplied)) {
- return i18n('AWS.amazonq.featureDev.pillText.continue')
- }
- if (files.some((file) => file.rejected || file.changeApplied)) {
- return i18n('AWS.amazonq.featureDev.pillText.acceptRemainingChanges')
- }
- return i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges')
- }
-
- public async computeFilePathDiff(filePath: NewFileInfo) {
- const leftPath = `${filePath.workspaceFolder.uri.fsPath}/${filePath.relativePath}`
- const rightPath = filePath.virtualMemoryUri.path
- const diff = await computeDiff(leftPath, rightPath, this.tabID, featureDevScheme)
- return { leftPath, rightPath, ...diff }
- }
-
- public async sendMetricDataTelemetry(operationName: string, result: string) {
- await this.proxyClient.sendMetricData({
- metricName: 'Operation',
- metricValue: 1,
- timestamp: new Date(),
- product: 'FeatureDev',
- dimensions: [
- {
- name: 'operationName',
- value: operationName,
- },
- {
- name: 'result',
- value: result,
- },
- ],
- })
- }
-
- public async sendLinesOfCodeGeneratedTelemetry() {
- let charactersOfCodeGenerated = 0
- let linesOfCodeGenerated = 0
- // deleteFiles are currently not counted because the number of lines added is always 0
- const filePaths = this.state.filePaths ?? []
- for (const filePath of filePaths) {
- const { leftPath, changes, charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
- const codeChangeKey = `${leftPath}#@${JSON.stringify(changes)}`
- if (this._reportedCodeChanges.has(codeChangeKey)) {
- continue
- }
- charactersOfCodeGenerated += charsAdded
- linesOfCodeGenerated += linesAdded
- this._reportedCodeChanges.add(codeChangeKey)
- }
- await this.proxyClient.sendFeatureDevCodeGenerationEvent({
- conversationId: this.conversationId,
- charactersOfCodeGenerated,
- linesOfCodeGenerated,
- })
- }
-
- public async sendLinesOfCodeAcceptedTelemetry(filePaths: NewFileInfo[]) {
- let charactersOfCodeAccepted = 0
- let linesOfCodeAccepted = 0
- for (const filePath of filePaths) {
- const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath)
- charactersOfCodeAccepted += charsAdded
- linesOfCodeAccepted += linesAdded
- }
- await this.proxyClient.sendFeatureDevCodeAcceptanceEvent({
- conversationId: this.conversationId,
- charactersOfCodeAccepted,
- linesOfCodeAccepted,
- })
- }
-
- get state() {
- if (!this._state) {
- throw new IllegalStateError("State should be initialized before it's read")
- }
- return this._state
- }
-
- get currentCodeGenerationId() {
- return this.state.currentCodeGenerationId
- }
-
- get uploadId() {
- if (!('uploadId' in this.state)) {
- throw new IllegalStateError("UploadId has to be initialized before it's read")
- }
- return this.state.uploadId
- }
-
- get retries() {
- return this.codeGenRetries
- }
-
- decreaseRetries() {
- this.codeGenRetries -= 1
- }
- get conversationId() {
- if (!this._conversationId) {
- throw new ConversationIdNotFoundError()
- }
- return this._conversationId
- }
-
- // Used for cases where it is not needed to have conversationId
- get conversationIdUnsafe() {
- return this._conversationId
- }
-
- get latestMessage() {
- return this._latestMessage
- }
-
- set latestMessage(msg: string) {
- this._latestMessage = msg
- }
-
- get telemetry() {
- return this._telemetry
- }
-
- get acceptCodeMessageId() {
- return this._acceptCodeMessageId
- }
-
- get acceptCodeTelemetrySent() {
- return this._acceptCodeTelemetrySent
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/session/sessionState.ts b/packages/core/src/amazonqFeatureDev/session/sessionState.ts
deleted file mode 100644
index 5879c16493f..00000000000
--- a/packages/core/src/amazonqFeatureDev/session/sessionState.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { MynahIcons } from '@aws/mynah-ui'
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { featureDevScheme } from '../constants'
-import {
- ApiClientError,
- ApiServiceError,
- IllegalStateTransition,
- NoChangeRequiredException,
- PromptRefusalException,
-} from '../errors'
-import {
- DeletedFileInfo,
- DevPhase,
- Intent,
- NewFileInfo,
- SessionState,
- SessionStateAction,
- SessionStateConfig,
- SessionStateInteraction,
-} from '../../amazonq/commons/types'
-import { registerNewFiles } from '../../amazonq/util/files'
-import { randomUUID } from '../../shared/crypto'
-import { collectFiles } from '../../shared/utilities/workspaceUtils'
-import { i18n } from '../../shared/i18n-helper'
-import { Messenger } from '../../amazonq/commons/connector/baseMessenger'
-import { FollowUpTypes } from '../../amazonq/commons/types'
-import {
- BaseCodeGenState,
- BaseMessenger,
- BasePrepareCodeGenState,
- CreateNextStateParams,
-} from '../../amazonq/session/sessionState'
-import { LlmError } from '../../amazonq/errors'
-
-export class ConversationNotStartedState implements Omit {
- public tokenSource: vscode.CancellationTokenSource
- public readonly phase = DevPhase.INIT
-
- constructor(public tabID: string) {
- this.tokenSource = new vscode.CancellationTokenSource()
- }
-
- async interact(_action: SessionStateAction): Promise {
- throw new IllegalStateTransition()
- }
-}
-
-export class MockCodeGenState implements SessionState {
- public tokenSource: vscode.CancellationTokenSource
- public filePaths: NewFileInfo[]
- public deletedFiles: DeletedFileInfo[]
- public readonly conversationId: string
- public readonly codeGenerationId?: string
- public readonly uploadId: string
-
- constructor(
- private config: SessionStateConfig,
- public tabID: string
- ) {
- this.tokenSource = new vscode.CancellationTokenSource()
- this.filePaths = []
- this.deletedFiles = []
- this.conversationId = this.config.conversationId
- this.uploadId = randomUUID()
- }
-
- async interact(action: SessionStateAction): Promise {
- // in a `mockcodegen` state, we should read from the `mock-data` folder and output
- // every file retrieved in the same shape the LLM would
- try {
- const files = await collectFiles(
- this.config.workspaceFolders.map((f) => path.join(f.uri.fsPath, './mock-data')),
- this.config.workspaceFolders,
- {
- excludeByGitIgnore: false,
- }
- )
- const newFileContents = files.map((f) => ({
- zipFilePath: f.zipFilePath,
- fileContent: f.fileContent,
- }))
- this.filePaths = registerNewFiles(
- action.fs,
- newFileContents,
- this.uploadId,
- this.config.workspaceFolders,
- this.conversationId,
- featureDevScheme
- )
- this.deletedFiles = [
- {
- zipFilePath: 'src/this-file-should-be-deleted.ts',
- workspaceFolder: this.config.workspaceFolders[0],
- relativePath: 'src/this-file-should-be-deleted.ts',
- rejected: false,
- changeApplied: false,
- },
- ]
- action.messenger.sendCodeResult(
- this.filePaths,
- this.deletedFiles,
- [
- {
- licenseName: 'MIT',
- repository: 'foo',
- url: 'foo',
- },
- ],
- this.tabID,
- this.uploadId,
- this.codeGenerationId ?? ''
- )
- action.messenger.sendAnswer({
- message: undefined,
- type: 'system-prompt',
- followUps: [
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'),
- type: FollowUpTypes.InsertCode,
- icon: 'ok' as MynahIcons,
- status: 'success',
- },
- {
- pillText: i18n('AWS.amazonq.featureDev.pillText.provideFeedback'),
- type: FollowUpTypes.ProvideFeedbackAndRegenerateCode,
- icon: 'refresh' as MynahIcons,
- status: 'info',
- },
- ],
- tabID: this.tabID,
- })
- } catch (e) {
- // TODO: handle this error properly, double check what would be expected behaviour if mock code does not work.
- getLogger().error('Unable to use mock code generation: %O', e)
- }
-
- return {
- // no point in iterating after a mocked code gen?
- nextState: this,
- interaction: {},
- }
- }
-}
-
-export class FeatureDevCodeGenState extends BaseCodeGenState {
- protected handleProgress(messenger: Messenger, action: SessionStateAction, detail?: string): void {
- if (detail) {
- messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.generatingCode') + `\n\n${detail}`,
- type: 'answer-part',
- tabID: this.tabID,
- })
- }
- }
-
- protected getScheme(): string {
- return featureDevScheme
- }
-
- protected getTimeoutErrorCode(): string {
- return 'CodeGenTimeout'
- }
-
- protected handleGenerationComplete(
- _messenger: Messenger,
- _newFileInfo: NewFileInfo[],
- action: SessionStateAction
- ): void {
- // No special handling needed for feature dev
- }
-
- protected handleError(messenger: BaseMessenger, codegenResult: any): Error {
- switch (true) {
- case codegenResult.codeGenerationStatusDetail?.includes('Guardrails'): {
- return new ApiClientError(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'GuardrailsException',
- 400
- )
- }
- case codegenResult.codeGenerationStatusDetail?.includes('PromptRefusal'): {
- return new PromptRefusalException()
- }
- case codegenResult.codeGenerationStatusDetail?.includes('EmptyPatch'): {
- if (codegenResult.codeGenerationStatusDetail?.includes('NO_CHANGE_REQUIRED')) {
- return new NoChangeRequiredException()
- }
- return new LlmError(i18n('AWS.amazonq.featureDev.error.codeGen.default'), {
- code: 'EmptyPatchException',
- })
- }
- case codegenResult.codeGenerationStatusDetail?.includes('Throttling'): {
- return new ApiClientError(
- i18n('AWS.amazonq.featureDev.error.throttling'),
- 'GetTaskAssistCodeGeneration',
- 'ThrottlingException',
- 429
- )
- }
- case codegenResult.codeGenerationStatusDetail?.includes('FileCreationFailed'): {
- return new ApiServiceError(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'FileCreationFailedException',
- 500
- )
- }
- default: {
- return new ApiServiceError(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'UnknownException',
- 500
- )
- }
- }
- }
-
- protected async startCodeGeneration(action: SessionStateAction, codeGenerationId: string): Promise {
- await this.config.proxyClient.startCodeGeneration(
- this.config.conversationId,
- this.config.uploadId,
- action.msg,
- Intent.DEV,
- codeGenerationId,
- this.currentCodeGenerationId
- )
-
- if (!this.isCancellationRequested) {
- action.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.generatingCode'),
- type: 'answer-part',
- tabID: this.tabID,
- })
- action.messenger.sendUpdatePlaceholder(this.tabID, i18n('AWS.amazonq.featureDev.pillText.generatingCode'))
- }
- }
-
- protected override createNextState(config: SessionStateConfig, params: CreateNextStateParams): SessionState {
- return super.createNextState(
- { ...config, currentCodeGenerationId: this.currentCodeGenerationId },
- params,
- FeatureDevPrepareCodeGenState
- )
- }
-}
-
-export class FeatureDevPrepareCodeGenState extends BasePrepareCodeGenState {
- protected preUpload(action: SessionStateAction): void {
- action.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.uploadingCode'),
- type: 'answer-part',
- tabID: this.tabID,
- })
-
- action.messenger.sendUpdatePlaceholder(this.tabID, i18n('AWS.amazonq.featureDev.pillText.uploadingCode'))
- }
-
- protected postUpload(action: SessionStateAction): void {
- if (!action.tokenSource?.token.isCancellationRequested) {
- action.messenger.sendAnswer({
- message: i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted'),
- type: 'answer-part',
- tabID: this.tabID,
- })
-
- action.messenger.sendUpdatePlaceholder(
- this.tabID,
- i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted')
- )
- }
- }
-
- protected override createNextState(config: SessionStateConfig): SessionState {
- return super.createNextState(config, FeatureDevCodeGenState)
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/storages/chatSession.ts b/packages/core/src/amazonqFeatureDev/storages/chatSession.ts
deleted file mode 100644
index f45576aa9df..00000000000
--- a/packages/core/src/amazonqFeatureDev/storages/chatSession.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { BaseChatSessionStorage } from '../../amazonq/commons/baseChatStorage'
-import { Messenger } from '../../amazonq/commons/connector/baseMessenger'
-import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import { featureDevScheme } from '../constants'
-import { Session } from '../session/session'
-
-export class FeatureDevChatSessionStorage extends BaseChatSessionStorage {
- constructor(protected readonly messenger: Messenger) {
- super()
- }
-
- override async createSession(tabID: string): Promise {
- const sessionConfig = await createSessionConfig(featureDevScheme)
- const session = new Session(sessionConfig, this.messenger, tabID)
- this.sessions.set(tabID, session)
- return session
- }
-}
diff --git a/packages/core/src/amazonqFeatureDev/userFacingText.ts b/packages/core/src/amazonqFeatureDev/userFacingText.ts
deleted file mode 100644
index 9b8a781ef1a..00000000000
--- a/packages/core/src/amazonqFeatureDev/userFacingText.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { manageAccessGuideURL } from '../amazonq/webview/ui/texts/constants'
-import { userGuideURL } from '../amazonq/webview/ui/texts/constants'
-import { featureName } from './constants'
-
-export const examples = `
-You can use /dev to:
-- Add a new feature or logic
-- Write tests
-- Fix a bug in your project
-- Generate a README for a file, folder, or project
-
-To learn more, visit the _[Amazon Q Developer User Guide](${userGuideURL})_.
-`
-
-export const uploadCodeError = `I'm sorry, I couldn't upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](${manageAccessGuideURL}) or contact your network or organization administrator.`
-
-// Utils for logging and showing customer facing conversation id text
-export const messageWithConversationId = (conversationId?: string) =>
- conversationId ? `\n\nConversation ID: **${conversationId}**` : ''
-export const logWithConversationId = (conversationId: string) => `${featureName} Conversation ID: ${conversationId}`
diff --git a/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts b/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts
deleted file mode 100644
index 5d92fb7188c..00000000000
--- a/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatControllerEventEmitters } from '../../controllers/chat/controller'
-import { MessageListener } from '../../../amazonq/messages/messageListener'
-import { ExtensionMessage } from '../../../amazonq/webview/ui/commands'
-
-export interface UIMessageListenerProps {
- readonly chatControllerEventEmitters: ChatControllerEventEmitters
- readonly webViewMessageListener: MessageListener
-}
-
-export class UIMessageListener {
- private featureDevControllerEventsEmitters: ChatControllerEventEmitters | undefined
- private webViewMessageListener: MessageListener
-
- constructor(props: UIMessageListenerProps) {
- this.featureDevControllerEventsEmitters = props.chatControllerEventEmitters
- this.webViewMessageListener = props.webViewMessageListener
-
- // Now we are listening to events that get sent from amazonq/webview/actions/actionListener (e.g. the tab)
- this.webViewMessageListener.onMessage((msg) => {
- this.handleMessage(msg)
- })
- }
-
- private handleMessage(msg: ExtensionMessage) {
- switch (msg.command) {
- case 'chat-prompt':
- this.processChatMessage(msg)
- break
- case 'follow-up-was-clicked':
- this.followUpClicked(msg)
- break
- case 'open-diff':
- this.openDiff(msg)
- break
- case 'chat-item-voted':
- this.chatItemVoted(msg)
- break
- case 'chat-item-feedback':
- this.chatItemFeedback(msg)
- break
- case 'stop-response':
- this.stopResponse(msg)
- break
- case 'new-tab-was-created':
- this.tabOpened(msg)
- break
- case 'tab-was-removed':
- this.tabClosed(msg)
- break
- case 'auth-follow-up-was-clicked':
- this.authClicked(msg)
- break
- case 'response-body-link-click':
- this.processResponseBodyLinkClick(msg)
- break
- case 'insert_code_at_cursor_position':
- this.insertCodeAtPosition(msg)
- break
- case 'file-click':
- this.fileClicked(msg)
- break
- case 'store-code-result-message-id':
- this.storeCodeResultMessageId(msg)
- break
- }
- }
-
- private chatItemVoted(msg: any) {
- this.featureDevControllerEventsEmitters?.processChatItemVotedMessage.fire({
- tabID: msg.tabID,
- command: msg.command,
- vote: msg.vote,
- messageId: msg.messageId,
- })
- }
-
- private chatItemFeedback(msg: any) {
- this.featureDevControllerEventsEmitters?.processChatItemFeedbackMessage.fire(msg)
- }
-
- private processChatMessage(msg: any) {
- this.featureDevControllerEventsEmitters?.processHumanChatMessage.fire({
- message: msg.chatMessage,
- tabID: msg.tabID,
- })
- }
-
- private followUpClicked(msg: any) {
- this.featureDevControllerEventsEmitters?.followUpClicked.fire({
- followUp: msg.followUp,
- tabID: msg.tabID,
- })
- }
-
- private fileClicked(msg: any) {
- this.featureDevControllerEventsEmitters?.fileClicked.fire({
- tabID: msg.tabID,
- filePath: msg.filePath,
- actionName: msg.actionName,
- messageId: msg.messageId,
- })
- }
-
- private openDiff(msg: any) {
- this.featureDevControllerEventsEmitters?.openDiff.fire({
- tabID: msg.tabID,
- filePath: msg.filePath,
- deleted: msg.deleted,
- messageId: msg.messageId,
- })
- }
-
- private stopResponse(msg: any) {
- this.featureDevControllerEventsEmitters?.stopResponse.fire({
- tabID: msg.tabID,
- })
- }
-
- private tabOpened(msg: any) {
- this.featureDevControllerEventsEmitters?.tabOpened.fire({
- tabID: msg.tabID,
- })
- }
-
- private tabClosed(msg: any) {
- this.featureDevControllerEventsEmitters?.tabClosed.fire({
- tabID: msg.tabID,
- })
- }
-
- private authClicked(msg: any) {
- this.featureDevControllerEventsEmitters?.authClicked.fire({
- tabID: msg.tabID,
- authType: msg.authType,
- })
- }
-
- private processResponseBodyLinkClick(msg: any) {
- this.featureDevControllerEventsEmitters?.processResponseBodyLinkClick.fire({
- command: msg.command,
- messageId: msg.messageId,
- tabID: msg.tabID,
- link: msg.link,
- })
- }
-
- private insertCodeAtPosition(msg: any) {
- this.featureDevControllerEventsEmitters?.insertCodeAtPositionClicked.fire({
- command: msg.command,
- messageId: msg.messageId,
- tabID: msg.tabID,
- code: msg.code,
- insertionTargetType: msg.insertionTargetType,
- codeReference: msg.codeReference,
- })
- }
-
- private storeCodeResultMessageId(msg: any) {
- this.featureDevControllerEventsEmitters?.storeCodeResultMessageId.fire({
- messageId: msg.messageId,
- tabID: msg.tabID,
- })
- }
-}
diff --git a/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts b/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts
index 63c1bfe3a2f..1646864e066 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts
@@ -8,12 +8,19 @@ import path from 'path'
import { FolderInfo, transformByQState } from '../../models/model'
import fs from '../../../shared/fs/fs'
import { createPomCopy, replacePomVersion } from './transformFileHandler'
-import { IManifestFile } from '../../../amazonqFeatureDev/models'
import { getLogger } from '../../../shared/logger/logger'
import { telemetry } from '../../../shared/telemetry/telemetry'
import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState'
import { MetadataResult } from '../../../shared/telemetry/telemetryClient'
+export interface IManifestFile {
+ pomArtifactId: string
+ pomFolderName: string
+ hilCapability: string
+ pomGroupId: string
+ sourcePomVersion: string
+}
+
/**
* @description This class helps encapsulate the "human in the loop" behavior of Amazon Q transform. Users
* will be prompted for input during the transformation process. Amazon Q will make some temporary folders
diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
index 88f34a799d1..2ec6fdb7c37 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
@@ -10,13 +10,13 @@ import xml2js = require('xml2js')
import * as CodeWhispererConstants from '../../models/constants'
import { existsSync, readFileSync, writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports
import { BuildSystem, DB, FolderInfo, transformByQState } from '../../models/model'
-import { IManifestFile } from '../../../amazonqFeatureDev/models'
import fs from '../../../shared/fs/fs'
import globals from '../../../shared/extensionGlobals'
import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession'
import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors'
import { getLogger } from '../../../shared/logger/logger'
import AdmZip from 'adm-zip'
+import { IManifestFile } from './humanInTheLoopManager'
export async function getDependenciesFolderInfo(): Promise {
const dependencyFolderName = `${CodeWhispererConstants.dependencyFolderName}${globals.clock.Date.now()}`
diff --git a/packages/core/src/shared/constants.ts b/packages/core/src/shared/constants.ts
index 48e64342e57..95d2aaac309 100644
--- a/packages/core/src/shared/constants.ts
+++ b/packages/core/src/shared/constants.ts
@@ -4,6 +4,7 @@
*/
import * as vscode from 'vscode'
+import { manageAccessGuideURL } from '../amazonq/webview/ui/texts/constants'
export const profileSettingKey = 'profile'
export const productName: string = 'aws-toolkit-vscode'
@@ -196,3 +197,11 @@ export const amazonQVscodeMarketplace =
export const crashMonitoringDirName = 'crashMonitoring'
export const amazonQTabSuffix = '(Generated by Amazon Q)'
+
+/**
+ * Common strings used throughout the application
+ */
+
+export const uploadCodeError = `I'm sorry, I couldn't upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](${manageAccessGuideURL}) or contact your network or organization administrator.`
+
+export const featureName = 'Amazon Q Developer Agent for software development'
diff --git a/packages/core/src/shared/errors.ts b/packages/core/src/shared/errors.ts
index 7cec122acaf..5d114043be3 100644
--- a/packages/core/src/shared/errors.ts
+++ b/packages/core/src/shared/errors.ts
@@ -15,7 +15,7 @@ import type * as os from 'os'
import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming'
import { driveLetterRegex } from './utilities/pathUtils'
import { getLogger } from './logger/logger'
-import { crashMonitoringDirName } from './constants'
+import { crashMonitoringDirName, uploadCodeError } from './constants'
import { RequestCancelledError } from './request'
let _username = 'unknown-user'
@@ -849,6 +849,21 @@ export class ServiceError extends ToolkitError {
}
}
+export class UploadURLExpired extends ClientError {
+ constructor() {
+ super(
+ "I’m sorry, I wasn't able to generate code. A connection timed out or became unavailable. Please try again or check the following:\n\n- Exclude non-essential files in your workspace’s .gitignore.\n\n- Check that your network connection is stable.",
+ { code: 'UploadURLExpired' }
+ )
+ }
+}
+
+export class UploadCodeError extends ServiceError {
+ constructor(statusCode: string) {
+ super(uploadCodeError, { code: `UploadCode-${statusCode}` })
+ }
+}
+
export class ContentLengthError extends ClientError {
constructor(message: string, info: ErrorInformation = { code: 'ContentLengthError' }) {
super(message, info)
diff --git a/packages/core/src/shared/utilities/workspaceUtils.ts b/packages/core/src/shared/utilities/workspaceUtils.ts
index 12cce75b3ff..122f2a185f4 100644
--- a/packages/core/src/shared/utilities/workspaceUtils.ts
+++ b/packages/core/src/shared/utilities/workspaceUtils.ts
@@ -19,7 +19,6 @@ import * as parser from '@gerhobbelt/gitignore-parser'
import fs from '../fs/fs'
import { ChildProcess } from './processUtils'
import { isWin } from '../vscode/env'
-import { maxRepoSizeBytes } from '../../amazonqFeatureDev/constants'
type GitIgnoreRelativeAcceptor = {
folderPath: string
@@ -378,6 +377,8 @@ export async function collectFiles(
const includeContent = options?.includeContent ?? true
const maxFileSizeBytes = options?.maxFileSizeBytes ?? 1024 * 1024 * 10
+ // Max allowed size for file collection
+ const maxRepoSizeBytes = 200 * 1024 * 1024
const excludeByGitIgnore = options?.excludeByGitIgnore ?? true
const failOnLimit = options?.failOnLimit ?? true
const inputExcludePatterns = options?.excludePatterns ?? defaultExcludePatterns
diff --git a/packages/core/src/test/amazonq/common/diff.test.ts b/packages/core/src/test/amazonq/common/diff.test.ts
index 0fc81403a59..a8f3bea8747 100644
--- a/packages/core/src/test/amazonq/common/diff.test.ts
+++ b/packages/core/src/test/amazonq/common/diff.test.ts
@@ -13,7 +13,6 @@ import * as path from 'path'
import * as vscode from 'vscode'
import sinon from 'sinon'
import { FileSystem } from '../../../shared/fs/fs'
-import { featureDevScheme } from '../../../amazonqFeatureDev'
import {
createAmazonQUri,
getFileDiffUris,
@@ -28,6 +27,7 @@ describe('diff', () => {
const filePath = path.join('/', 'foo', 'fi')
const rightPath = path.join('foo', 'fee')
const tabId = '0'
+ const featureDevScheme = 'aws-featureDev'
let sandbox: sinon.SinonSandbox
let executeCommandSpy: sinon.SinonSpy
diff --git a/packages/core/src/test/amazonq/session/sessionState.test.ts b/packages/core/src/test/amazonq/session/sessionState.test.ts
deleted file mode 100644
index dcff3398cea..00000000000
--- a/packages/core/src/test/amazonq/session/sessionState.test.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import sinon from 'sinon'
-import { CodeGenBase } from '../../../amazonq/session/sessionState'
-import { RunCommandLogFileName } from '../../../amazonq/session/sessionState'
-import assert from 'assert'
-import * as workspaceUtils from '../../../shared/utilities/workspaceUtils'
-import { TelemetryHelper } from '../../../amazonq/util/telemetryHelper'
-import { assertLogsContain } from '../../globalSetup.test'
-
-describe('CodeGenBase generateCode log file handling', () => {
- class TestCodeGen extends CodeGenBase {
- public generatedFiles: any[] = []
- constructor(config: any, tabID: string) {
- super(config, tabID)
- }
- protected handleProgress(_messenger: any): void {
- // No-op for test.
- }
- protected getScheme(): string {
- return 'file'
- }
- protected getTimeoutErrorCode(): string {
- return 'test_timeout'
- }
- protected handleGenerationComplete(_messenger: any, newFileInfo: any[]): void {
- this.generatedFiles = newFileInfo
- }
- protected handleError(_messenger: any, _codegenResult: any): Error {
- throw new Error('handleError called')
- }
- }
-
- let fakeProxyClient: any
- let testConfig: any
- let fsMock: any
- let messengerMock: any
- let testAction: any
-
- beforeEach(async () => {
- const ret = {
- testworkspacefolder: {
- uri: vscode.Uri.file('/path/to/testworkspacefolder'),
- name: 'testworkspacefolder',
- index: 0,
- },
- }
- sinon.stub(workspaceUtils, 'getWorkspaceFoldersByPrefixes').returns(ret)
-
- fakeProxyClient = {
- getCodeGeneration: sinon.stub().resolves({
- codeGenerationStatus: { status: 'Complete' },
- codeGenerationRemainingIterationCount: 0,
- codeGenerationTotalIterationCount: 1,
- }),
- exportResultArchive: sinon.stub(),
- }
-
- testConfig = {
- conversationId: 'conv_test',
- uploadId: 'upload_test',
- workspaceRoots: ['/path/to/testworkspacefolder'],
- proxyClient: fakeProxyClient,
- }
-
- fsMock = {
- writeFile: sinon.stub().resolves(),
- registerProvider: sinon.stub().resolves(),
- }
-
- messengerMock = { sendAnswer: sinon.spy() }
-
- testAction = {
- fs: fsMock,
- messenger: messengerMock,
- tokenSource: {
- token: {
- isCancellationRequested: false,
- onCancellationRequested: () => {},
- },
- },
- }
- })
-
- afterEach(() => {
- sinon.restore()
- })
-
- const runGenerateCode = async (codeGenerationId: string) => {
- const testCodeGen = new TestCodeGen(testConfig, 'tab1')
- return await testCodeGen.generateCode({
- messenger: messengerMock,
- fs: fsMock,
- codeGenerationId,
- telemetry: new TelemetryHelper(),
- workspaceFolders: [testConfig.workspaceRoots[0]],
- action: testAction,
- })
- }
-
- const createExpectedNewFile = (fileObj: { zipFilePath: string; fileContent: string }) => ({
- zipFilePath: fileObj.zipFilePath,
- fileContent: fileObj.fileContent,
- changeApplied: false,
- rejected: false,
- relativePath: fileObj.zipFilePath,
- virtualMemoryUri: vscode.Uri.file(`/upload_test/${fileObj.zipFilePath}`),
- workspaceFolder: {
- index: 0,
- name: 'testworkspacefolder',
- uri: vscode.Uri.file('/path/to/testworkspacefolder'),
- },
- })
-
- it('adds the log content to logger if present and excludes it from new files', async () => {
- const logFileInfo = {
- zipFilePath: RunCommandLogFileName,
- fileContent: 'Log content',
- }
- const otherFile = { zipFilePath: 'other.ts', fileContent: 'other content' }
- fakeProxyClient.exportResultArchive.resolves({
- newFileContents: [logFileInfo, otherFile],
- deletedFiles: [],
- references: [],
- })
- const result = await runGenerateCode('codegen1')
-
- assertLogsContain(`sessionState: Run Command logs, Log content`, false, 'info')
-
- const expectedNewFile = createExpectedNewFile(otherFile)
- assert.deepStrictEqual(result.newFiles[0].fileContent, expectedNewFile.fileContent)
- })
-
- it('skips log file handling if log file is not present', async () => {
- const file1 = { zipFilePath: 'file1.ts', fileContent: 'content1' }
- fakeProxyClient.exportResultArchive.resolves({
- newFileContents: [file1],
- deletedFiles: [],
- references: [],
- })
-
- const result = await runGenerateCode('codegen2')
-
- assert.throws(() => assertLogsContain(`sessionState: Run Command logs, Log content`, false, 'info'))
-
- const expectedNewFile = createExpectedNewFile(file1)
- assert.deepStrictEqual(result.newFiles[0].fileContent, expectedNewFile.fileContent)
- })
-})
diff --git a/packages/core/src/test/amazonq/session/testSetup.ts b/packages/core/src/test/amazonq/session/testSetup.ts
deleted file mode 100644
index 76f2c90f94f..00000000000
--- a/packages/core/src/test/amazonq/session/testSetup.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import sinon from 'sinon'
-import { createBasicTestConfig, createMockSessionStateConfig, TestSessionMocks } from '../utils'
-import { SessionStateConfig } from '../../../amazonq'
-
-export function createSessionTestSetup() {
- const conversationId = 'conversation-id'
- const uploadId = 'upload-id'
- const tabId = 'tab-id'
- const currentCodeGenerationId = ''
-
- return {
- conversationId,
- uploadId,
- tabId,
- currentCodeGenerationId,
- }
-}
-
-export async function createTestConfig(
- testMocks: TestSessionMocks,
- conversationId: string,
- uploadId: string,
- currentCodeGenerationId: string
-) {
- testMocks.getCodeGeneration = sinon.stub()
- testMocks.exportResultArchive = sinon.stub()
- testMocks.createUploadUrl = sinon.stub()
- const basicConfig = await createBasicTestConfig(conversationId, uploadId, currentCodeGenerationId)
- const testConfig = createMockSessionStateConfig(basicConfig, testMocks)
- return testConfig
-}
-
-export interface TestContext {
- conversationId: string
- uploadId: string
- tabId: string
- currentCodeGenerationId: string
- testConfig: SessionStateConfig
- testMocks: Record
-}
-
-export function createTestContext(): TestContext {
- const { conversationId, uploadId, tabId, currentCodeGenerationId } = createSessionTestSetup()
-
- return {
- conversationId,
- uploadId,
- tabId,
- currentCodeGenerationId,
- testConfig: {} as SessionStateConfig,
- testMocks: {},
- }
-}
-
-export function setupTestHooks(context: TestContext) {
- beforeEach(async () => {
- context.testMocks = {}
- context.testConfig = await createTestConfig(
- context.testMocks,
- context.conversationId,
- context.uploadId,
- context.currentCodeGenerationId
- )
- })
-
- afterEach(() => {
- sinon.restore()
- })
-}
diff --git a/packages/core/src/test/amazonq/utils.ts b/packages/core/src/test/amazonq/utils.ts
deleted file mode 100644
index ec2e7020e4e..00000000000
--- a/packages/core/src/test/amazonq/utils.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as sinon from 'sinon'
-import { MessagePublisher } from '../../amazonq/messages/messagePublisher'
-import { ChatControllerEventEmitters, FeatureDevController } from '../../amazonqFeatureDev/controllers/chat/controller'
-import { FeatureDevChatSessionStorage } from '../../amazonqFeatureDev/storages/chatSession'
-import { createTestWorkspaceFolder } from '../testUtil'
-import { Session } from '../../amazonqFeatureDev/session/session'
-import { SessionState, SessionStateAction, SessionStateConfig } from '../../amazonq/commons/types'
-import { FeatureDevClient } from '../../amazonqFeatureDev/client/featureDev'
-import { VirtualMemoryFile } from '../../shared/virtualMemoryFile'
-import path from 'path'
-import { featureDevChat } from '../../amazonqFeatureDev/constants'
-import { Messenger } from '../../amazonq/commons/connector/baseMessenger'
-import { AppToWebViewMessageDispatcher } from '../../amazonq/commons/connector/connectorMessages'
-import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import { VirtualFileSystem } from '../../shared'
-import { TelemetryHelper } from '../../amazonq/util/telemetryHelper'
-import { FeatureClient } from '../../amazonq/client/client'
-
-export function createMessenger(): Messenger {
- return new Messenger(
- new AppToWebViewMessageDispatcher(new MessagePublisher(sinon.createStubInstance(vscode.EventEmitter))),
- featureDevChat
- )
-}
-
-export function createMockChatEmitters(): ChatControllerEventEmitters {
- return {
- processHumanChatMessage: new vscode.EventEmitter(),
- followUpClicked: new vscode.EventEmitter(),
- openDiff: new vscode.EventEmitter(),
- processChatItemVotedMessage: new vscode.EventEmitter(),
- processChatItemFeedbackMessage: new vscode.EventEmitter(),
- stopResponse: new vscode.EventEmitter(),
- tabOpened: new vscode.EventEmitter(),
- tabClosed: new vscode.EventEmitter(),
- authClicked: new vscode.EventEmitter(),
- processResponseBodyLinkClick: new vscode.EventEmitter(),
- insertCodeAtPositionClicked: new vscode.EventEmitter(),
- fileClicked: new vscode.EventEmitter(),
- storeCodeResultMessageId: new vscode.EventEmitter(),
- }
-}
-
-export interface ControllerSetup {
- emitters: ChatControllerEventEmitters
- workspaceFolder: vscode.WorkspaceFolder
- messenger: Messenger
- sessionStorage: FeatureDevChatSessionStorage
-}
-
-export async function createSession({
- messenger,
- sessionState,
- scheme,
- conversationID = '0',
- tabID = '0',
- uploadID = '0',
-}: {
- messenger: Messenger
- scheme: string
- sessionState?: Omit
- conversationID?: string
- tabID?: string
- uploadID?: string
-}) {
- const sessionConfig = await createSessionConfig(scheme)
-
- const client = sinon.createStubInstance(FeatureDevClient)
- client.createConversation.resolves(conversationID)
- const session = new Session(sessionConfig, messenger, tabID, sessionState, client)
-
- sinon.stub(session, 'conversationId').get(() => conversationID)
- sinon.stub(session, 'uploadId').get(() => uploadID)
-
- return session
-}
-
-export async function sessionRegisterProvider(session: Session, uri: vscode.Uri, fileContents: Uint8Array) {
- session.config.fs.registerProvider(uri, new VirtualMemoryFile(fileContents))
-}
-
-export function generateVirtualMemoryUri(uploadID: string, filePath: string, scheme: string) {
- const generationFilePath = path.join(uploadID, filePath)
- const uri = vscode.Uri.from({ scheme, path: generationFilePath })
- return uri
-}
-
-export async function sessionWriteFile(session: Session, uri: vscode.Uri, encodedContent: Uint8Array) {
- await session.config.fs.writeFile(uri, encodedContent, {
- create: true,
- overwrite: true,
- })
-}
-
-export async function createController(): Promise {
- const messenger = createMessenger()
-
- // Create a new workspace root
- const testWorkspaceFolder = await createTestWorkspaceFolder()
- sinon.stub(vscode.workspace, 'workspaceFolders').value([testWorkspaceFolder])
-
- const sessionStorage = new FeatureDevChatSessionStorage(messenger)
-
- const mockChatControllerEventEmitters = createMockChatEmitters()
-
- new FeatureDevController(
- mockChatControllerEventEmitters,
- messenger,
- sessionStorage,
- sinon.createStubInstance(vscode.EventEmitter).event
- )
-
- return {
- emitters: mockChatControllerEventEmitters,
- workspaceFolder: testWorkspaceFolder,
- messenger,
- sessionStorage,
- }
-}
-
-export function createMockSessionStateAction(msg?: string): SessionStateAction {
- return {
- task: 'test-task',
- msg: msg ?? 'test-msg',
- fs: new VirtualFileSystem(),
- messenger: new Messenger(
- new AppToWebViewMessageDispatcher(new MessagePublisher(new vscode.EventEmitter())),
- featureDevChat
- ),
- telemetry: new TelemetryHelper(),
- uploadHistory: {},
- }
-}
-
-export interface TestSessionMocks {
- getCodeGeneration?: sinon.SinonStub
- exportResultArchive?: sinon.SinonStub
- createUploadUrl?: sinon.SinonStub
-}
-
-export interface SessionTestConfig {
- conversationId: string
- uploadId: string
- workspaceFolder: vscode.WorkspaceFolder
- currentCodeGenerationId?: string
-}
-
-export function createMockSessionStateConfig(config: SessionTestConfig, mocks: TestSessionMocks): SessionStateConfig {
- return {
- workspaceRoots: ['fake-source'],
- workspaceFolders: [config.workspaceFolder],
- conversationId: config.conversationId,
- proxyClient: {
- createConversation: () => sinon.stub(),
- createUploadUrl: () => mocks.createUploadUrl!(),
- startCodeGeneration: () => sinon.stub(),
- getCodeGeneration: () => mocks.getCodeGeneration!(),
- exportResultArchive: () => mocks.exportResultArchive!(),
- } as unknown as FeatureClient,
- uploadId: config.uploadId,
- currentCodeGenerationId: config.currentCodeGenerationId,
- }
-}
-
-export async function createBasicTestConfig(
- conversationId: string = 'conversation-id',
- uploadId: string = 'upload-id',
- currentCodeGenerationId: string = ''
-): Promise {
- return {
- conversationId,
- uploadId,
- workspaceFolder: await createTestWorkspaceFolder('fake-root'),
- currentCodeGenerationId,
- }
-}
diff --git a/packages/core/src/test/amazonqDoc/controller.test.ts b/packages/core/src/test/amazonqDoc/controller.test.ts
deleted file mode 100644
index d69edc47fd7..00000000000
--- a/packages/core/src/test/amazonqDoc/controller.test.ts
+++ /dev/null
@@ -1,577 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as vscode from 'vscode'
-import sinon from 'sinon'
-import {
- assertTelemetry,
- ControllerSetup,
- createController,
- createExpectedEvent,
- createExpectedMetricData,
- createSession,
- EventMetrics,
- FollowUpSequences,
- generateVirtualMemoryUri,
- updateFilePaths,
-} from './utils'
-import { CurrentWsFolders, MetricDataOperationName, MetricDataResult, NewFileInfo } from '../../amazonqDoc/types'
-import { DocCodeGenState, docScheme, Session } from '../../amazonqDoc'
-import { AuthUtil } from '../../codewhisperer'
-import {
- ApiClientError,
- ApiServiceError,
- CodeIterationLimitError,
- FeatureDevClient,
- getMetricResult,
- MonthlyConversationLimitError,
- PrepareRepoFailedError,
- TabIdNotFoundError,
- UploadCodeError,
- UploadURLExpired,
- UserMessageNotFoundError,
- ZipFileError,
-} from '../../amazonqFeatureDev'
-import { i18n, ToolkitError, waitUntil } from '../../shared'
-import { FollowUpTypes } from '../../amazonq/commons/types'
-import { FileSystem } from '../../shared/fs/fs'
-import { ReadmeBuilder } from './mockContent'
-import * as path from 'path'
-import {
- ContentLengthError,
- NoChangeRequiredException,
- PromptRefusalException,
- PromptTooVagueError,
- PromptUnrelatedError,
- ReadmeTooLargeError,
- ReadmeUpdateTooLargeError,
- WorkspaceEmptyError,
-} from '../../amazonqDoc/errors'
-import { LlmError } from '../../amazonq/errors'
-describe('Controller - Doc Generation', () => {
- const firstTabID = '123'
- const firstConversationID = '123'
- const firstUploadID = '123'
-
- const secondTabID = '456'
- const secondConversationID = '456'
- const secondUploadID = '456'
-
- let controllerSetup: ControllerSetup
- let session: Session
- let sendDocTelemetrySpy: sinon.SinonStub
- let sendDocTelemetrySpyForSecondTab: sinon.SinonStub
- let mockGetCodeGeneration: sinon.SinonStub
- let getSessionStub: sinon.SinonStub
- let modifiedReadme: string
- const generatedReadme = ReadmeBuilder.createBaseReadme()
- let sandbox: sinon.SinonSandbox
-
- const getFilePaths = (controllerSetup: ControllerSetup, uploadID: string): NewFileInfo[] => [
- {
- zipFilePath: path.normalize('README.md'),
- relativePath: path.normalize('README.md'),
- fileContent: generatedReadme,
- rejected: false,
- virtualMemoryUri: generateVirtualMemoryUri(uploadID, path.normalize('README.md'), docScheme),
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- ]
-
- async function createCodeGenState(
- sandbox: sinon.SinonSandbox,
- tabID: string,
- conversationID: string,
- uploadID: string
- ) {
- mockGetCodeGeneration = sandbox.stub().resolves({ codeGenerationStatus: { status: 'Complete' } })
-
- const workspaceFolders = [controllerSetup.workspaceFolder] as CurrentWsFolders
- const testConfig = {
- conversationId: conversationID,
- proxyClient: {
- createConversation: () => sandbox.stub(),
- createUploadUrl: () => sandbox.stub(),
- generatePlan: () => sandbox.stub(),
- startCodeGeneration: () => sandbox.stub(),
- getCodeGeneration: () => mockGetCodeGeneration(),
- exportResultArchive: () => sandbox.stub(),
- } as unknown as FeatureDevClient,
- workspaceRoots: [''],
- uploadId: uploadID,
- workspaceFolders,
- }
-
- const codeGenState = new DocCodeGenState(
- testConfig,
- getFilePaths(controllerSetup, uploadID),
- [],
- [],
- tabID,
- 0,
- {}
- )
- return createSession({
- messenger: controllerSetup.messenger,
- sessionState: codeGenState,
- conversationID,
- tabID,
- uploadID,
- scheme: docScheme,
- sandbox,
- })
- }
- async function fireFollowUps(followUpTypes: FollowUpTypes[], stub: sinon.SinonStub, tabID: string) {
- for (const type of followUpTypes) {
- controllerSetup.emitters.followUpClicked.fire({
- tabID,
- followUp: { type },
- })
- await waitForStub(stub)
- }
- }
-
- async function waitForStub(stub: sinon.SinonStub) {
- await waitUntil(() => Promise.resolve(stub.callCount > 0), {})
- }
-
- async function performAction(
- action: 'generate' | 'update' | 'makeChanges' | 'accept' | 'edit',
- getSessionStub: sinon.SinonStub,
- message?: string,
- tabID = firstTabID,
- conversationID = firstConversationID
- ) {
- const sequences = {
- generate: FollowUpSequences.generateReadme,
- update: FollowUpSequences.updateReadme,
- edit: FollowUpSequences.editReadme,
- makeChanges: FollowUpSequences.makeChanges,
- accept: FollowUpSequences.acceptContent,
- }
-
- await fireFollowUps(sequences[action], getSessionStub, tabID)
-
- if ((action === 'makeChanges' || action === 'edit') && message) {
- controllerSetup.emitters.processHumanChatMessage.fire({
- tabID,
- conversationID,
- message,
- })
- await waitForStub(getSessionStub)
- }
- }
-
- async function setupTest(sandbox: sinon.SinonSandbox, isMultiTabs?: boolean, error?: ToolkitError) {
- controllerSetup = await createController(sandbox)
- session = await createCodeGenState(sandbox, firstTabID, firstConversationID, firstUploadID)
- sendDocTelemetrySpy = sandbox.stub(session, 'sendDocTelemetryEvent').resolves()
- sandbox.stub(session, 'preloader').resolves()
- error ? sandbox.stub(session, 'send').throws(error) : sandbox.stub(session, 'send').resolves()
- Object.defineProperty(session, '_conversationId', {
- value: firstConversationID,
- writable: true,
- configurable: true,
- })
-
- sandbox.stub(AuthUtil.instance, 'getChatAuthState').resolves({
- codewhispererCore: 'connected',
- codewhispererChat: 'connected',
- amazonQ: 'connected',
- })
- sandbox.stub(FileSystem.prototype, 'exists').resolves(false)
- if (isMultiTabs) {
- const secondSession = await createCodeGenState(sandbox, secondTabID, secondConversationID, secondUploadID)
- sendDocTelemetrySpyForSecondTab = sandbox.stub(secondSession, 'sendDocTelemetryEvent').resolves()
- sandbox.stub(secondSession, 'preloader').resolves()
- sandbox.stub(secondSession, 'send').resolves()
- Object.defineProperty(secondSession, '_conversationId', {
- value: secondConversationID,
- writable: true,
- configurable: true,
- })
- getSessionStub = sandbox
- .stub(controllerSetup.sessionStorage, 'getSession')
- .callsFake(async (tabId: string): Promise => {
- if (tabId === firstTabID) {
- return session
- }
- if (tabId === secondTabID) {
- return secondSession
- }
- throw new Error(`Unknown tab ID: ${tabId}`)
- })
- } else {
- getSessionStub = sandbox.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- }
- modifiedReadme = ReadmeBuilder.createReadmeWithRepoStructure()
- sandbox
- .stub(vscode.workspace, 'openTextDocument')
- .callsFake(async (options?: string | vscode.Uri | { language?: string; content?: string }) => {
- let documentPath = ''
- if (typeof options === 'string') {
- documentPath = options
- } else if (options && 'path' in options) {
- documentPath = options.path
- }
-
- const isTempFile = documentPath === 'empty'
- return {
- getText: () => (isTempFile ? generatedReadme : modifiedReadme),
- } as any
- })
- }
-
- const retryTest = async (
- testMethod: () => Promise,
- isMultiTabs?: boolean,
- error?: ToolkitError,
- maxRetries: number = 3,
- delayMs: number = 1000
- ): Promise => {
- let lastError: Error | undefined
-
- for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
- sandbox = sinon.createSandbox()
- sandbox.useFakeTimers({
- now: new Date('2025-03-20T12:00:00.000Z'),
- toFake: ['Date'],
- })
- try {
- await setupTest(sandbox, isMultiTabs, error)
- await testMethod()
- sandbox.restore()
- return
- } catch (error) {
- lastError = error as Error
- sandbox.restore()
-
- if (attempt > maxRetries) {
- console.error(`Test failed after ${maxRetries} retries:`, lastError)
- throw lastError
- }
-
- console.log(`Test attempt ${attempt} failed, retrying...`)
- await new Promise((resolve) => setTimeout(resolve, delayMs))
- }
- }
- }
-
- after(() => {
- if (sandbox) {
- sandbox.restore()
- }
- })
-
- it('should emit generation telemetry for initial README generation', async () => {
- await retryTest(async () => {
- await performAction('generate', getSessionStub)
-
- const expectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.INITIAL_README,
- interactionType: 'GENERATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'generation',
- sandbox,
- })
- })
- })
- it('should emit another generation telemetry for make changes operation after initial README generation', async () => {
- await retryTest(async () => {
- await performAction('generate', getSessionStub)
- const firstExpectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.INITIAL_README,
- interactionType: 'GENERATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent: firstExpectedEvent,
- type: 'generation',
- sandbox,
- })
-
- await updateFilePaths(session, modifiedReadme, firstUploadID, docScheme, controllerSetup.workspaceFolder)
- await performAction('makeChanges', getSessionStub, 'add repository structure section')
-
- const secondExpectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'GENERATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent: secondExpectedEvent,
- type: 'generation',
- sandbox,
- })
- })
- })
-
- it('should emit acceptance telemetry for README generation', async () => {
- await retryTest(async () => {
- await performAction('generate', getSessionStub)
- await new Promise((resolve) => setTimeout(resolve, 100))
- const expectedEvent = createExpectedEvent({
- type: 'acceptance',
- ...EventMetrics.INITIAL_README,
- interactionType: 'GENERATE_README',
- conversationId: firstConversationID,
- })
-
- await performAction('accept', getSessionStub)
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'acceptance',
- sandbox,
- })
- })
- })
- it('should emit generation telemetry for README update', async () => {
- await retryTest(async () => {
- await performAction('update', getSessionStub)
-
- const expectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'UPDATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'generation',
- sandbox,
- })
- })
- })
- it('should emit another generation telemetry for make changes operation after README update', async () => {
- await retryTest(async () => {
- await performAction('update', getSessionStub)
- await new Promise((resolve) => setTimeout(resolve, 100))
-
- modifiedReadme = ReadmeBuilder.createReadmeWithDataFlow()
- await updateFilePaths(session, modifiedReadme, firstUploadID, docScheme, controllerSetup.workspaceFolder)
-
- await performAction('makeChanges', getSessionStub, 'add data flow section')
-
- const expectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.DATA_FLOW,
- interactionType: 'UPDATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'generation',
- sandbox,
- })
- })
- })
-
- it('should emit acceptance telemetry for README update', async () => {
- await retryTest(async () => {
- await performAction('update', getSessionStub)
- await new Promise((resolve) => setTimeout(resolve, 100))
-
- const expectedEvent = createExpectedEvent({
- type: 'acceptance',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'UPDATE_README',
- conversationId: firstConversationID,
- })
-
- await performAction('accept', getSessionStub)
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'acceptance',
- sandbox,
- })
- })
- })
-
- it('should emit generation telemetry for README edit', async () => {
- await retryTest(async () => {
- await performAction('edit', getSessionStub, 'add repository structure section')
-
- const expectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'EDIT_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'generation',
- sandbox,
- })
- })
- })
- it('should emit acceptance telemetry for README edit', async () => {
- await retryTest(async () => {
- await performAction('edit', getSessionStub, 'add repository structure section')
- await new Promise((resolve) => setTimeout(resolve, 100))
-
- const expectedEvent = createExpectedEvent({
- type: 'acceptance',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'EDIT_README',
- conversationId: firstConversationID,
- })
-
- await performAction('accept', getSessionStub)
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'acceptance',
- sandbox,
- })
- })
- })
- it('should emit separate telemetry events when executing /doc in different tabs', async () => {
- await retryTest(async () => {
- const firstSession = await getSessionStub(firstTabID)
- const secondSession = await getSessionStub(secondTabID)
- await performAction('generate', firstSession)
- await performAction('update', secondSession, undefined, secondTabID, secondConversationID)
-
- const expectedEvent = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.INITIAL_README,
- interactionType: 'GENERATE_README',
- conversationId: firstConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent,
- type: 'generation',
- sandbox,
- })
-
- const expectedEventForSecondTab = createExpectedEvent({
- type: 'generation',
- ...EventMetrics.REPO_STRUCTURE,
- interactionType: 'UPDATE_README',
- conversationId: secondConversationID,
- })
-
- await assertTelemetry({
- spy: sendDocTelemetrySpyForSecondTab,
- expectedEvent: expectedEventForSecondTab,
- type: 'generation',
- sandbox,
- })
- }, true)
- })
-
- describe('Doc Generation Error Handling', () => {
- const errors = [
- {
- name: 'MonthlyConversationLimitError',
- error: new MonthlyConversationLimitError('Service Quota Exceeded'),
- },
- {
- name: 'DocGenerationGuardrailsException',
- error: new ApiClientError(
- i18n('AWS.amazonq.doc.error.docGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'GuardrailsException',
- 400
- ),
- },
- {
- name: 'DocGenerationEmptyPatchException',
- error: new LlmError(i18n('AWS.amazonq.doc.error.docGen.default'), {
- code: 'EmptyPatchException',
- }),
- },
- {
- name: 'DocGenerationThrottlingException',
- error: new ApiClientError(
- i18n('AWS.amazonq.featureDev.error.throttling'),
- 'GetTaskAssistCodeGeneration',
- 'ThrottlingException',
- 429
- ),
- },
- { name: 'UploadCodeError', error: new UploadCodeError('403: Forbiden') },
- { name: 'UserMessageNotFoundError', error: new UserMessageNotFoundError() },
- { name: 'TabIdNotFoundError', error: new TabIdNotFoundError() },
- { name: 'PrepareRepoFailedError', error: new PrepareRepoFailedError() },
- { name: 'PromptRefusalException', error: new PromptRefusalException(0) },
- { name: 'ZipFileError', error: new ZipFileError() },
- { name: 'CodeIterationLimitError', error: new CodeIterationLimitError() },
- { name: 'UploadURLExpired', error: new UploadURLExpired() },
- { name: 'NoChangeRequiredException', error: new NoChangeRequiredException() },
- { name: 'ReadmeTooLargeError', error: new ReadmeTooLargeError() },
- { name: 'ReadmeUpdateTooLargeError', error: new ReadmeUpdateTooLargeError(0) },
- { name: 'ContentLengthError', error: new ContentLengthError() },
- { name: 'WorkspaceEmptyError', error: new WorkspaceEmptyError() },
- { name: 'PromptUnrelatedError', error: new PromptUnrelatedError(0) },
- { name: 'PromptTooVagueError', error: new PromptTooVagueError(0) },
- { name: 'PromptRefusalException', error: new PromptRefusalException(0) },
- {
- name: 'default',
- error: new ApiServiceError(
- i18n('AWS.amazonq.doc.error.docGen.default'),
- 'GetTaskAssistCodeGeneration',
- 'UnknownException',
- 500
- ),
- },
- ]
- for (const { name, error } of errors) {
- it(`should emit failure operation telemetry when ${name} occurs`, async () => {
- await retryTest(
- async () => {
- await performAction('generate', getSessionStub)
-
- const expectedSuccessMetric = createExpectedMetricData(
- MetricDataOperationName.StartDocGeneration,
- MetricDataResult.Success
- )
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent: expectedSuccessMetric,
- type: 'metric',
- sandbox,
- })
-
- const expectedFailureMetric = createExpectedMetricData(
- MetricDataOperationName.EndDocGeneration,
- getMetricResult(error)
- )
- await assertTelemetry({
- spy: sendDocTelemetrySpy,
- expectedEvent: expectedFailureMetric,
- type: 'metric',
- sandbox,
- })
- },
- undefined,
- error
- )
- })
- }
- })
-})
diff --git a/packages/core/src/test/amazonqDoc/mockContent.ts b/packages/core/src/test/amazonqDoc/mockContent.ts
deleted file mode 100644
index 1f3e68f6a58..00000000000
--- a/packages/core/src/test/amazonqDoc/mockContent.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-export const ReadmeSections = {
- HEADER: `# My Awesome Project
-
-This is a demo project showcasing various features and capabilities.`,
-
- GETTING_STARTED: `## Getting Started
-1. Clone the repository
-2. Run npm install
-3. Start the application`,
-
- FEATURES: `## Features
-- Fast processing
-- Easy to use
-- Well documented`,
-
- LICENSE: '## License\nMIT License',
-
- REPO_STRUCTURE: `## Repository Structure
-/src
- /components
- /utils
-/tests
- /unit
-/docs`,
-
- DATA_FLOW: `## Data Flow
-1. Input processing
- - Data validation
- - Format conversion
-2. Core processing
- - Business logic
- - Data transformation
-3. Output generation
- - Result formatting
- - Response delivery`,
-} as const
-
-export class ReadmeBuilder {
- private sections: string[] = []
-
- addSection(section: string): this {
- this.sections.push(section.replace(/\r\n/g, '\n'))
- return this
- }
-
- build(): string {
- return this.sections.join('\n\n').replace(/\r\n/g, '\n')
- }
-
- static createBaseReadme(): string {
- return new ReadmeBuilder()
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.HEADER))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.GETTING_STARTED))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.FEATURES))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.LICENSE))
- .build()
- }
-
- static createReadmeWithRepoStructure(): string {
- return new ReadmeBuilder()
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.HEADER))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.REPO_STRUCTURE))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.GETTING_STARTED))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.FEATURES))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.LICENSE))
- .build()
- }
-
- static createReadmeWithDataFlow(): string {
- return new ReadmeBuilder()
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.HEADER))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.GETTING_STARTED))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.FEATURES))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.DATA_FLOW))
- .addSection(ReadmeBuilder.normalizeSection(ReadmeSections.LICENSE))
- .build()
- }
-
- private static normalizeSection(section: string): string {
- return section.replace(/\r\n/g, '\n')
- }
-}
diff --git a/packages/core/src/test/amazonqDoc/session/sessionState.test.ts b/packages/core/src/test/amazonqDoc/session/sessionState.test.ts
deleted file mode 100644
index 8f96894cc22..00000000000
--- a/packages/core/src/test/amazonqDoc/session/sessionState.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import assert from 'assert'
-import sinon from 'sinon'
-import { DocPrepareCodeGenState } from '../../../amazonqDoc'
-import { createMockSessionStateAction } from '../../amazonq/utils'
-
-import { createTestContext, setupTestHooks } from '../../amazonq/session/testSetup'
-
-describe('sessionStateDoc', () => {
- const context = createTestContext()
- setupTestHooks(context)
-
- describe('DocPrepareCodeGenState', () => {
- it('error when failing to prepare repo information', async () => {
- sinon.stub(vscode.workspace, 'findFiles').throws()
- context.testMocks.createUploadUrl!.resolves({ uploadId: '', uploadUrl: '' })
- const testAction = createMockSessionStateAction()
-
- await assert.rejects(() => {
- return new DocPrepareCodeGenState(context.testConfig, [], [], [], context.tabId, 0).interact(testAction)
- })
- })
- })
-})
diff --git a/packages/core/src/test/amazonqDoc/utils.ts b/packages/core/src/test/amazonqDoc/utils.ts
deleted file mode 100644
index 51c7305902c..00000000000
--- a/packages/core/src/test/amazonqDoc/utils.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as sinon from 'sinon'
-import { MessagePublisher } from '../../amazonq/messages/messagePublisher'
-import { ChatControllerEventEmitters, DocController } from '../../amazonqDoc/controllers/chat/controller'
-import { DocChatSessionStorage } from '../../amazonqDoc/storages/chatSession'
-import { createTestWorkspaceFolder } from '../testUtil'
-import { Session } from '../../amazonqDoc/session/session'
-import { NewFileInfo, SessionState } from '../../amazonqDoc/types'
-import { FeatureDevClient } from '../../amazonqFeatureDev/client/featureDev'
-import { VirtualMemoryFile } from '../../shared/virtualMemoryFile'
-import path from 'path'
-import { docChat } from '../../amazonqDoc/constants'
-import { DocMessenger } from '../../amazonqDoc/messenger'
-import { AppToWebViewMessageDispatcher } from '../../amazonq/commons/connector/connectorMessages'
-import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory'
-import {
- DocV2GenerationEvent,
- DocV2AcceptanceEvent,
- MetricData,
-} from '../../amazonqFeatureDev/client/featuredevproxyclient'
-import { FollowUpTypes } from '../../amazonq/commons/types'
-
-export function createMessenger(sandbox: sinon.SinonSandbox): DocMessenger {
- return new DocMessenger(
- new AppToWebViewMessageDispatcher(new MessagePublisher(sandbox.createStubInstance(vscode.EventEmitter))),
- docChat
- )
-}
-
-export function createMockChatEmitters(): ChatControllerEventEmitters {
- return {
- processHumanChatMessage: new vscode.EventEmitter(),
- followUpClicked: new vscode.EventEmitter(),
- openDiff: new vscode.EventEmitter(),
- processChatItemVotedMessage: new vscode.EventEmitter(),
- processChatItemFeedbackMessage: new vscode.EventEmitter(),
- stopResponse: new vscode.EventEmitter(),
- tabOpened: new vscode.EventEmitter(),
- tabClosed: new vscode.EventEmitter(),
- authClicked: new vscode.EventEmitter(),
- processResponseBodyLinkClick: new vscode.EventEmitter(),
- insertCodeAtPositionClicked: new vscode.EventEmitter(),
- fileClicked: new vscode.EventEmitter(),
- formActionClicked: new vscode.EventEmitter(),
- }
-}
-
-export interface ControllerSetup {
- emitters: ChatControllerEventEmitters
- workspaceFolder: vscode.WorkspaceFolder
- messenger: DocMessenger
- sessionStorage: DocChatSessionStorage
-}
-
-export async function createSession({
- messenger,
- sessionState,
- scheme,
- conversationID = '0',
- tabID = '0',
- uploadID = '0',
- sandbox,
-}: {
- messenger: DocMessenger
- scheme: string
- sessionState?: Omit
- conversationID?: string
- tabID?: string
- uploadID?: string
- sandbox: sinon.SinonSandbox
-}) {
- const sessionConfig = await createSessionConfig(scheme)
-
- const client = sandbox.createStubInstance(FeatureDevClient)
- client.createConversation.resolves(conversationID)
- const session = new Session(sessionConfig, messenger, tabID, sessionState, client)
-
- sandbox.stub(session, 'conversationId').get(() => conversationID)
- sandbox.stub(session, 'uploadId').get(() => uploadID)
-
- return session
-}
-export async function sessionRegisterProvider(session: Session, uri: vscode.Uri, fileContents: Uint8Array) {
- session.config.fs.registerProvider(uri, new VirtualMemoryFile(fileContents))
-}
-
-export function generateVirtualMemoryUri(uploadID: string, filePath: string, scheme: string) {
- const generationFilePath = path.join(uploadID, filePath)
- const uri = vscode.Uri.from({ scheme, path: generationFilePath })
- return uri
-}
-
-export async function sessionWriteFile(session: Session, uri: vscode.Uri, encodedContent: Uint8Array) {
- await session.config.fs.writeFile(uri, encodedContent, {
- create: true,
- overwrite: true,
- })
-}
-
-export async function createController(sandbox: sinon.SinonSandbox): Promise {
- const messenger = createMessenger(sandbox)
-
- // Create a new workspace root
- const testWorkspaceFolder = await createTestWorkspaceFolder()
- sandbox.stub(vscode.workspace, 'workspaceFolders').value([testWorkspaceFolder])
-
- const sessionStorage = new DocChatSessionStorage(messenger)
-
- const mockChatControllerEventEmitters = createMockChatEmitters()
-
- new DocController(
- mockChatControllerEventEmitters,
- messenger,
- sessionStorage,
- sandbox.createStubInstance(vscode.EventEmitter).event
- )
-
- return {
- emitters: mockChatControllerEventEmitters,
- workspaceFolder: testWorkspaceFolder,
- messenger,
- sessionStorage,
- }
-}
-
-export type EventParams = {
- type: 'generation' | 'acceptance'
- chars: number
- lines: number
- files: number
- interactionType: 'GENERATE_README' | 'UPDATE_README' | 'EDIT_README'
- callIndex?: number
- conversationId: string
-}
-/**
- * Metrics for measuring README content changes in documentation generation tests.
- */
-export const EventMetrics = {
- /**
- * Initial README content measurements
- * Generated using ReadmeBuilder.createBaseReadme()
- */
- INITIAL_README: {
- chars: 265,
- lines: 16,
- files: 1,
- },
- /**
- * Repository Structure section measurements
- * Differential metrics when adding repository structure documentation compare to the initial readme
- */
- REPO_STRUCTURE: {
- chars: 60,
- lines: 8,
- files: 1,
- },
- /**
- * Data Flow section measurements
- * Differential metrics when adding data flow documentation compare to the initial readme
- */
- DATA_FLOW: {
- chars: 180,
- lines: 11,
- files: 1,
- },
-} as const
-
-export function createExpectedEvent(params: EventParams) {
- const baseEvent = {
- conversationId: params.conversationId,
- numberOfNavigations: 1,
- folderLevel: 'ENTIRE_WORKSPACE',
- interactionType: params.interactionType,
- }
-
- if (params.type === 'generation') {
- return {
- ...baseEvent,
- numberOfGeneratedChars: params.chars,
- numberOfGeneratedLines: params.lines,
- numberOfGeneratedFiles: params.files,
- } as DocV2GenerationEvent
- } else {
- return {
- ...baseEvent,
- numberOfAddedChars: params.chars,
- numberOfAddedLines: params.lines,
- numberOfAddedFiles: params.files,
- userDecision: 'ACCEPT',
- } as DocV2AcceptanceEvent
- }
-}
-
-export function createExpectedMetricData(operationName: string, result: string) {
- return {
- metricName: 'Operation',
- metricValue: 1,
- timestamp: new Date(),
- product: 'DocGeneration',
- dimensions: [
- {
- name: 'operationName',
- value: operationName,
- },
- {
- name: 'result',
- value: result,
- },
- ],
- }
-}
-
-export async function assertTelemetry(params: {
- spy: sinon.SinonStub
- expectedEvent: DocV2GenerationEvent | DocV2AcceptanceEvent | MetricData
- type: 'generation' | 'acceptance' | 'metric'
- sandbox: sinon.SinonSandbox
-}) {
- await new Promise((resolve) => setTimeout(resolve, 100))
- params.sandbox.assert.calledWith(params.spy, params.sandbox.match(params.expectedEvent), params.type)
-}
-
-export async function updateFilePaths(
- session: Session,
- content: string,
- uploadId: string,
- docScheme: string,
- workspaceFolder: any
-) {
- const updatedFilePaths: NewFileInfo[] = [
- {
- zipFilePath: path.normalize('README.md'),
- relativePath: path.normalize('README.md'),
- fileContent: content,
- rejected: false,
- virtualMemoryUri: generateVirtualMemoryUri(uploadId, path.normalize('README.md'), docScheme),
- workspaceFolder: workspaceFolder,
- changeApplied: false,
- },
- ]
-
- Object.defineProperty(session.state, 'filePaths', {
- get: () => updatedFilePaths,
- configurable: true,
- })
-}
-
-export const FollowUpSequences = {
- generateReadme: [FollowUpTypes.NewTask, FollowUpTypes.CreateDocumentation, FollowUpTypes.ProceedFolderSelection],
- updateReadme: [
- FollowUpTypes.NewTask,
- FollowUpTypes.UpdateDocumentation,
- FollowUpTypes.SynchronizeDocumentation,
- FollowUpTypes.ProceedFolderSelection,
- ],
- editReadme: [
- FollowUpTypes.NewTask,
- FollowUpTypes.UpdateDocumentation,
- FollowUpTypes.EditDocumentation,
- FollowUpTypes.ProceedFolderSelection,
- ],
- makeChanges: [FollowUpTypes.MakeChanges],
- acceptContent: [FollowUpTypes.AcceptChanges],
-}
diff --git a/packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts b/packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts
deleted file mode 100644
index 7848d0561b0..00000000000
--- a/packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts
+++ /dev/null
@@ -1,717 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as assert from 'assert'
-import * as path from 'path'
-import sinon from 'sinon'
-import { waitUntil } from '../../../../shared/utilities/timeoutUtils'
-import { ControllerSetup, createController, createSession, generateVirtualMemoryUri } from '../../../amazonq/utils'
-import {
- CurrentWsFolders,
- DeletedFileInfo,
- MetricDataOperationName,
- MetricDataResult,
- NewFileInfo,
-} from '../../../../amazonq/commons/types'
-import { Session } from '../../../../amazonqFeatureDev/session/session'
-import { Prompter } from '../../../../shared/ui/prompter'
-import { assertTelemetry, toFile } from '../../../testUtil'
-import {
- CodeIterationLimitError,
- ContentLengthError,
- createUserFacingErrorMessage,
- FeatureDevServiceError,
- getMetricResult,
- MonthlyConversationLimitError,
- NoChangeRequiredException,
- PrepareRepoFailedError,
- PromptRefusalException,
- SelectedFolderNotInWorkspaceFolderError,
- TabIdNotFoundError,
- UploadCodeError,
- UploadURLExpired,
- UserMessageNotFoundError,
- ZipFileError,
-} from '../../../../amazonqFeatureDev/errors'
-import {
- FeatureDevCodeGenState,
- FeatureDevPrepareCodeGenState,
-} from '../../../../amazonqFeatureDev/session/sessionState'
-import { FeatureDevClient } from '../../../../amazonqFeatureDev/client/featureDev'
-import { createAmazonQUri } from '../../../../amazonq/commons/diff'
-import { AuthUtil } from '../../../../codewhisperer'
-import { featureDevScheme, featureName, messageWithConversationId } from '../../../../amazonqFeatureDev'
-import { i18n } from '../../../../shared/i18n-helper'
-import { FollowUpTypes } from '../../../../amazonq/commons/types'
-import { ToolkitError } from '../../../../shared'
-import { MessengerTypes } from '../../../../amazonqFeatureDev/controllers/chat/messenger/constants'
-
-let mockGetCodeGeneration: sinon.SinonStub
-describe('Controller', () => {
- const tabID = '123'
- const conversationID = '456'
- const uploadID = '789'
-
- let session: Session
- let controllerSetup: ControllerSetup
-
- const getFilePaths = (controllerSetup: ControllerSetup): NewFileInfo[] => [
- {
- zipFilePath: 'myfile1.js',
- relativePath: 'myfile1.js',
- fileContent: '',
- rejected: false,
- virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'myfile1.js', featureDevScheme),
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- {
- zipFilePath: 'myfile2.js',
- relativePath: 'myfile2.js',
- fileContent: '',
- rejected: true,
- virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'myfile2.js', featureDevScheme),
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- ]
-
- const getDeletedFiles = (): DeletedFileInfo[] => [
- {
- zipFilePath: 'myfile3.js',
- relativePath: 'myfile3.js',
- rejected: false,
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- {
- zipFilePath: 'myfile4.js',
- relativePath: 'myfile4.js',
- rejected: true,
- workspaceFolder: controllerSetup.workspaceFolder,
- changeApplied: false,
- },
- ]
-
- async function createCodeGenState() {
- mockGetCodeGeneration = sinon.stub().resolves({ codeGenerationStatus: { status: 'Complete' } })
-
- const workspaceFolders = [controllerSetup.workspaceFolder] as CurrentWsFolders
- const testConfig = {
- conversationId: conversationID,
- proxyClient: {
- createConversation: () => sinon.stub(),
- createUploadUrl: () => sinon.stub(),
- generatePlan: () => sinon.stub(),
- startCodeGeneration: () => sinon.stub(),
- getCodeGeneration: () => mockGetCodeGeneration(),
- exportResultArchive: () => sinon.stub(),
- } as unknown as FeatureDevClient,
- workspaceRoots: [''],
- uploadId: uploadID,
- workspaceFolders,
- }
-
- const codeGenState = new FeatureDevCodeGenState(testConfig, getFilePaths(controllerSetup), [], [], tabID, 0, {})
- const newSession = await createSession({
- messenger: controllerSetup.messenger,
- sessionState: codeGenState,
- conversationID,
- tabID,
- uploadID,
- scheme: featureDevScheme,
- })
- return newSession
- }
-
- before(() => {
- sinon.stub(performance, 'now').returns(0)
- })
-
- beforeEach(async () => {
- controllerSetup = await createController()
- session = await createSession({
- messenger: controllerSetup.messenger,
- conversationID,
- tabID,
- uploadID,
- scheme: featureDevScheme,
- })
-
- sinon.stub(AuthUtil.instance, 'getChatAuthState').resolves({
- codewhispererCore: 'connected',
- codewhispererChat: 'connected',
- amazonQ: 'connected',
- })
- })
-
- afterEach(() => {
- sinon.restore()
- })
-
- describe('openDiff', async () => {
- async function openDiff(filePath: string, deleted = false) {
- const executeDiff = sinon.stub(vscode.commands, 'executeCommand').returns(Promise.resolve(undefined))
- controllerSetup.emitters.openDiff.fire({ tabID, conversationID, filePath, deleted })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(executeDiff.callCount > 0)
- }, {})
-
- return executeDiff
- }
-
- it('uses empty file when file is not found locally', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- const executedDiff = await openDiff(path.join('src', 'mynewfile.js'))
- assert.strictEqual(
- executedDiff.calledWith(
- 'vscode.diff',
- createAmazonQUri('empty', tabID, featureDevScheme),
- createAmazonQUri(path.join(uploadID, 'src', 'mynewfile.js'), tabID, featureDevScheme)
- ),
- true
- )
-
- assertTelemetry('amazonq_isReviewedChanges', { amazonqConversationId: conversationID, enabled: true })
- })
-
- it('uses file location when file is found locally and /src is not available', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- const newFileLocation = path.join(controllerSetup.workspaceFolder.uri.fsPath, 'mynewfile.js')
- await toFile('', newFileLocation)
- const executedDiff = await openDiff('mynewfile.js')
- assert.strictEqual(
- executedDiff.calledWith(
- 'vscode.diff',
- vscode.Uri.file(newFileLocation),
- createAmazonQUri(path.join(uploadID, 'mynewfile.js'), tabID, featureDevScheme)
- ),
- true
- )
-
- assertTelemetry('amazonq_isReviewedChanges', { amazonqConversationId: conversationID, enabled: true })
- })
-
- it('uses file location when file is found locally and /src is available', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- const newFileLocation = path.join(controllerSetup.workspaceFolder.uri.fsPath, 'src', 'mynewfile.js')
- await toFile('', newFileLocation)
- const executedDiff = await openDiff(path.join('src', 'mynewfile.js'))
- assert.strictEqual(
- executedDiff.calledWith(
- 'vscode.diff',
- vscode.Uri.file(newFileLocation),
- createAmazonQUri(path.join(uploadID, 'src', 'mynewfile.js'), tabID, featureDevScheme)
- ),
- true
- )
-
- assertTelemetry('amazonq_isReviewedChanges', { amazonqConversationId: conversationID, enabled: true })
- })
-
- it('uses file location when file is found locally and source folder was picked', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- const newFileLocation = path.join(controllerSetup.workspaceFolder.uri.fsPath, 'foo', 'fi', 'mynewfile.js')
- await toFile('', newFileLocation)
- sinon.stub(vscode.workspace, 'getWorkspaceFolder').returns(controllerSetup.workspaceFolder)
- session.config.workspaceRoots = [path.join(controllerSetup.workspaceFolder.uri.fsPath, 'foo', 'fi')]
- const executedDiff = await openDiff(path.join('foo', 'fi', 'mynewfile.js'))
- assert.strictEqual(
- executedDiff.calledWith(
- 'vscode.diff',
- vscode.Uri.file(newFileLocation),
- createAmazonQUri(path.join(uploadID, 'foo', 'fi', 'mynewfile.js'), tabID, featureDevScheme)
- ),
- true
- )
-
- assertTelemetry('amazonq_isReviewedChanges', { amazonqConversationId: conversationID, enabled: true })
- })
- })
-
- describe('modifyDefaultSourceFolder', () => {
- async function modifyDefaultSourceFolder(sourceRoot: string) {
- const promptStub = sinon.stub(Prompter.prototype, 'prompt').resolves(vscode.Uri.file(sourceRoot))
- controllerSetup.emitters.followUpClicked.fire({
- tabID,
- followUp: {
- type: FollowUpTypes.ModifyDefaultSourceFolder,
- },
- })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(promptStub.callCount > 0)
- }, {})
-
- return controllerSetup.sessionStorage.getSession(tabID)
- }
-
- it('fails if selected folder is not under a workspace folder', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- sinon.stub(vscode.workspace, 'getWorkspaceFolder').returns(undefined)
- const messengerSpy = sinon.spy(controllerSetup.messenger, 'sendAnswer')
- await modifyDefaultSourceFolder('../../')
- assert.deepStrictEqual(
- messengerSpy.calledWith({
- tabID,
- type: 'answer',
- message: new SelectedFolderNotInWorkspaceFolderError().message,
- canBeVoted: true,
- }),
- true
- )
- assert.deepStrictEqual(
- messengerSpy.calledWith({
- tabID,
- type: 'system-prompt',
- followUps: sinon.match.any,
- }),
- true
- )
- })
-
- it('accepts valid source folders under a workspace root', async () => {
- sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- sinon.stub(vscode.workspace, 'getWorkspaceFolder').returns(controllerSetup.workspaceFolder)
- const expectedSourceRoot = path.join(controllerSetup.workspaceFolder.uri.fsPath, 'src')
- const modifiedSession = await modifyDefaultSourceFolder(expectedSourceRoot)
- assert.strictEqual(modifiedSession.config.workspaceRoots.length, 1)
- assert.strictEqual(modifiedSession.config.workspaceRoots[0], expectedSourceRoot)
- })
- })
-
- describe('newTask', () => {
- async function newTaskClicked() {
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
-
- controllerSetup.emitters.followUpClicked.fire({
- tabID,
- followUp: {
- type: FollowUpTypes.NewTask,
- },
- })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 0)
- }, {})
- }
-
- it('end chat telemetry is sent', async () => {
- await newTaskClicked()
-
- assertTelemetry('amazonq_endChat', { amazonqConversationId: conversationID, result: 'Succeeded' })
- })
- })
-
- describe('fileClicked', () => {
- async function fileClicked(
- getSessionStub: sinon.SinonStub<[tabID: string], Promise>,
- action: string,
- filePath: string
- ) {
- controllerSetup.emitters.fileClicked.fire({
- tabID,
- conversationID,
- filePath,
- action,
- })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 0)
- }, {})
- return getSessionStub.getCall(0).returnValue
- }
-
- it('clicking the "Reject File" button updates the file state to "rejected: true"', async () => {
- const filePath = getFilePaths(controllerSetup)[0].zipFilePath
- const session = await createCodeGenState()
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
-
- const rejectFile = await fileClicked(getSessionStub, 'reject-change', filePath)
- assert.strictEqual(rejectFile.state.filePaths?.find((i) => i.relativePath === filePath)?.rejected, true)
- })
-
- it('clicking the "Reject File" button and then "Revert Reject File", updates the file state to "rejected: false"', async () => {
- const filePath = getFilePaths(controllerSetup)[0].zipFilePath
- const session = await createCodeGenState()
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
-
- await fileClicked(getSessionStub, 'reject-change', filePath)
- const revertRejection = await fileClicked(getSessionStub, 'revert-rejection', filePath)
- assert.strictEqual(
- revertRejection.state.filePaths?.find((i) => i.relativePath === filePath)?.rejected,
- false
- )
- })
- })
-
- describe('insertCode', () => {
- it('sets the number of files accepted counting also deleted files', async () => {
- async function insertCode() {
- const initialState = new FeatureDevPrepareCodeGenState(
- {
- conversationId: conversationID,
- proxyClient: new FeatureDevClient(),
- workspaceRoots: [''],
- workspaceFolders: [controllerSetup.workspaceFolder],
- uploadId: uploadID,
- },
- getFilePaths(controllerSetup),
- getDeletedFiles(),
- [],
- tabID,
- 0
- )
-
- const newSession = await createSession({
- messenger: controllerSetup.messenger,
- sessionState: initialState,
- conversationID,
- tabID,
- uploadID,
- scheme: featureDevScheme,
- })
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(newSession)
-
- controllerSetup.emitters.followUpClicked.fire({
- tabID,
- conversationID,
- followUp: {
- type: FollowUpTypes.InsertCode,
- },
- })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 0)
- }, {})
- }
-
- await insertCode()
-
- assertTelemetry('amazonq_isAcceptedCodeChanges', {
- amazonqConversationId: conversationID,
- amazonqNumberOfFilesAccepted: 2,
- enabled: true,
- result: 'Succeeded',
- })
- })
- })
-
- describe('processUserChatMessage', function () {
- // TODO: fix disablePreviousFileList error
- const runs = [
- { name: 'ContentLengthError', error: new ContentLengthError() },
- {
- name: 'MonthlyConversationLimitError',
- error: new MonthlyConversationLimitError('Service Quota Exceeded'),
- },
- {
- name: 'FeatureDevServiceErrorGuardrailsException',
- error: new FeatureDevServiceError(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- 'GuardrailsException'
- ),
- },
- {
- name: 'FeatureDevServiceErrorEmptyPatchException',
- error: new FeatureDevServiceError(
- i18n('AWS.amazonq.featureDev.error.throttling'),
- 'EmptyPatchException'
- ),
- },
- {
- name: 'FeatureDevServiceErrorThrottlingException',
- error: new FeatureDevServiceError(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- 'ThrottlingException'
- ),
- },
- { name: 'UploadCodeError', error: new UploadCodeError('403: Forbiden') },
- { name: 'UserMessageNotFoundError', error: new UserMessageNotFoundError() },
- { name: 'TabIdNotFoundError', error: new TabIdNotFoundError() },
- { name: 'PrepareRepoFailedError', error: new PrepareRepoFailedError() },
- { name: 'PromptRefusalException', error: new PromptRefusalException() },
- { name: 'ZipFileError', error: new ZipFileError() },
- { name: 'CodeIterationLimitError', error: new CodeIterationLimitError() },
- { name: 'UploadURLExpired', error: new UploadURLExpired() },
- { name: 'NoChangeRequiredException', error: new NoChangeRequiredException() },
- { name: 'default', error: new ToolkitError('Default', { code: 'Default' }) },
- ]
-
- async function fireChatMessage(session: Session) {
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
-
- controllerSetup.emitters.processHumanChatMessage.fire({
- tabID,
- conversationID,
- message: 'test message',
- })
-
- /**
- * Wait until the controller has time to process the event
- * Sessions should be called twice:
- * 1. When the session getWorkspaceRoot is called
- * 2. When the controller processes preloader
- */
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 1)
- }, {})
- }
-
- describe('onCodeGeneration', function () {
- let session: any
- let sendMetricDataTelemetrySpy: sinon.SinonStub
-
- async function verifyException(error: ToolkitError) {
- sinon.stub(session, 'send').throws(error)
-
- await fireChatMessage(session)
- await verifyMetricsCalled()
- assert.ok(
- sendMetricDataTelemetrySpy.calledWith(
- MetricDataOperationName.StartCodeGeneration,
- MetricDataResult.Success
- )
- )
- const metricResult = getMetricResult(error)
- assert.ok(
- sendMetricDataTelemetrySpy.calledWith(MetricDataOperationName.EndCodeGeneration, metricResult)
- )
- }
-
- async function verifyMetricsCalled() {
- await waitUntil(() => Promise.resolve(sendMetricDataTelemetrySpy.callCount >= 2), {})
- }
-
- async function verifyMessage(
- expectedMessage: string,
- type: MessengerTypes,
- remainingIterations?: number,
- totalIterations?: number
- ) {
- sinon.stub(session, 'send').resolves()
- sinon.stub(session, 'sendLinesOfCodeGeneratedTelemetry').resolves() // Avoid sending extra telemetry
- const sendAnswerSpy = sinon.stub(controllerSetup.messenger, 'sendAnswer')
- sinon.stub(session.state, 'codeGenerationRemainingIterationCount').value(remainingIterations)
- sinon.stub(session.state, 'codeGenerationTotalIterationCount').value(totalIterations)
-
- await fireChatMessage(session)
- await verifyMetricsCalled()
-
- assert.ok(
- sendAnswerSpy.calledWith({
- type,
- tabID,
- message: expectedMessage,
- })
- )
- }
-
- beforeEach(async () => {
- session = await createCodeGenState()
- sinon.stub(session, 'preloader').resolves()
- sendMetricDataTelemetrySpy = sinon.stub(session, 'sendMetricDataTelemetry')
- })
-
- it('sends success operation telemetry', async () => {
- sinon.stub(session, 'send').resolves()
- sinon.stub(session, 'sendLinesOfCodeGeneratedTelemetry').resolves() // Avoid sending extra telemetry
-
- await fireChatMessage(session)
- await verifyMetricsCalled()
-
- assert.ok(
- sendMetricDataTelemetrySpy.calledWith(
- MetricDataOperationName.StartCodeGeneration,
- MetricDataResult.Success
- )
- )
- assert.ok(
- sendMetricDataTelemetrySpy.calledWith(
- MetricDataOperationName.EndCodeGeneration,
- MetricDataResult.Success
- )
- )
- })
-
- for (const { name, error } of runs) {
- it(`sends failure operation telemetry on ${name}`, async () => {
- await verifyException(error)
- })
- }
-
- // Using 3 to avoid spamming the tests
- for (let remainingIterations = 0; remainingIterations <= 3; remainingIterations++) {
- it(`verifies add code messages for remaining iterations at ${remainingIterations}`, async () => {
- const totalIterations = 10
- const expectedMessage = (() => {
- if (remainingIterations > 2) {
- return 'Would you like me to add this code to your project, or provide feedback for new code?'
- } else if (remainingIterations > 0) {
- return `Would you like me to add this code to your project, or provide feedback for new code? You have ${remainingIterations} out of ${totalIterations} code generations left.`
- } else {
- return 'Would you like me to add this code to your project?'
- }
- })()
- await verifyMessage(expectedMessage, 'answer', remainingIterations, totalIterations)
- })
- }
-
- for (let remainingIterations = -1; remainingIterations <= 3; remainingIterations++) {
- let remaining: number | undefined = remainingIterations
- if (remainingIterations < 0) {
- remaining = undefined
- }
- it(`verifies messages after cancellation for remaining iterations at ${remaining !== undefined ? remaining : 'undefined'}`, async () => {
- const totalIterations = 10
- const expectedMessage = (() => {
- if (remaining === undefined || remaining > 2) {
- return 'I stopped generating your code. If you want to continue working on this task, provide another description.'
- } else if (remaining > 0) {
- return `I stopped generating your code. If you want to continue working on this task, provide another description. You have ${remaining} out of ${totalIterations} code generations left.`
- } else {
- return "I stopped generating your code. You don't have more iterations left, however, you can start a new session."
- }
- })()
- session.state.tokenSource.cancel()
- await verifyMessage(
- expectedMessage,
- 'answer-part',
- remaining,
- remaining === undefined ? undefined : totalIterations
- )
- })
- }
- })
-
- describe('processErrorChatMessage', function () {
- function createTestErrorMessage(message: string) {
- return createUserFacingErrorMessage(`${featureName} request failed: ${message}`)
- }
-
- async function verifyException(error: ToolkitError) {
- sinon.stub(session, 'preloader').throws(error)
- const sendAnswerSpy = sinon.stub(controllerSetup.messenger, 'sendAnswer')
- const sendErrorMessageSpy = sinon.stub(controllerSetup.messenger, 'sendErrorMessage')
- const sendMonthlyLimitErrorSpy = sinon.stub(controllerSetup.messenger, 'sendMonthlyLimitError')
-
- await fireChatMessage(session)
-
- switch (error.constructor.name) {
- case ContentLengthError.name:
- assert.ok(
- sendAnswerSpy.calledWith({
- type: 'answer',
- tabID,
- message: error.message + messageWithConversationId(session?.conversationIdUnsafe),
- canBeVoted: true,
- })
- )
- break
- case MonthlyConversationLimitError.name:
- assert.ok(sendMonthlyLimitErrorSpy.calledWith(tabID))
- break
- case FeatureDevServiceError.name:
- case UploadCodeError.name:
- case UserMessageNotFoundError.name:
- case TabIdNotFoundError.name:
- case PrepareRepoFailedError.name:
- assert.ok(
- sendErrorMessageSpy.calledWith(
- createTestErrorMessage(error.message),
- tabID,
- session?.retries,
- session?.conversationIdUnsafe
- )
- )
- break
- case PromptRefusalException.name:
- case ZipFileError.name:
- assert.ok(
- sendErrorMessageSpy.calledWith(
- createTestErrorMessage(error.message),
- tabID,
- 0,
- session?.conversationIdUnsafe,
- true
- )
- )
- break
- case NoChangeRequiredException.name:
- case CodeIterationLimitError.name:
- case UploadURLExpired.name:
- assert.ok(
- sendAnswerSpy.calledWith({
- type: 'answer',
- tabID,
- message: error.message,
- canBeVoted: true,
- })
- )
- break
- default:
- assert.ok(
- sendErrorMessageSpy.calledWith(
- i18n('AWS.amazonq.featureDev.error.codeGen.default'),
- tabID,
- session?.retries,
- session?.conversationIdUnsafe,
- true
- )
- )
- break
- }
- }
-
- for (const run of runs) {
- it(`should handle ${run.name}`, async function () {
- await verifyException(run.error)
- })
- }
- })
- })
-
- describe('stopResponse', () => {
- it('should emit ui_click telemetry with elementId amazonq_stopCodeGeneration', async () => {
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
- controllerSetup.emitters.stopResponse.fire({ tabID, conversationID })
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 0)
- }, {})
- assertTelemetry('ui_click', { elementId: 'amazonq_stopCodeGeneration' })
- })
- })
-
- describe('closeSession', async () => {
- async function closeSessionClicked() {
- const getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
-
- controllerSetup.emitters.followUpClicked.fire({
- tabID,
- followUp: {
- type: FollowUpTypes.CloseSession,
- },
- })
-
- // Wait until the controller has time to process the event
- await waitUntil(() => {
- return Promise.resolve(getSessionStub.callCount > 0)
- }, {})
- }
-
- it('end chat telemetry is sent', async () => {
- await closeSessionClicked()
-
- assertTelemetry('amazonq_endChat', { amazonqConversationId: conversationID, result: 'Succeeded' })
- })
- })
-})
diff --git a/packages/core/src/test/amazonqFeatureDev/session/sessionState.test.ts b/packages/core/src/test/amazonqFeatureDev/session/sessionState.test.ts
deleted file mode 100644
index 2d68654ee00..00000000000
--- a/packages/core/src/test/amazonqFeatureDev/session/sessionState.test.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import assert from 'assert'
-import sinon from 'sinon'
-import {
- MockCodeGenState,
- FeatureDevPrepareCodeGenState,
- FeatureDevCodeGenState,
-} from '../../../amazonqFeatureDev/session/sessionState'
-import { ToolkitError } from '../../../shared/errors'
-import * as crypto from '../../../shared/crypto'
-import { createMockSessionStateAction } from '../../amazonq/utils'
-
-import { createTestContext, setupTestHooks } from '../../amazonq/session/testSetup'
-
-describe('sessionStateFeatureDev', () => {
- const context = createTestContext()
- setupTestHooks(context)
-
- describe('MockCodeGenState', () => {
- it('loops forever in the same state', async () => {
- sinon.stub(crypto, 'randomUUID').returns('upload-id' as ReturnType<(typeof crypto)['randomUUID']>)
- const testAction = createMockSessionStateAction()
- const state = new MockCodeGenState(context.testConfig, context.tabId)
- const result = await state.interact(testAction)
-
- assert.deepStrictEqual(result, {
- nextState: state,
- interaction: {},
- })
- })
- })
-
- describe('FeatureDevPrepareCodeGenState', () => {
- it('error when failing to prepare repo information', async () => {
- sinon.stub(vscode.workspace, 'findFiles').throws()
- context.testMocks.createUploadUrl!.resolves({ uploadId: '', uploadUrl: '' })
- const testAction = createMockSessionStateAction()
-
- await assert.rejects(() => {
- return new FeatureDevPrepareCodeGenState(context.testConfig, [], [], [], context.tabId, 0).interact(
- testAction
- )
- })
- })
- })
-
- describe('FeatureDevCodeGenState', () => {
- it('transitions to FeatureDevPrepareCodeGenState when codeGenerationStatus ready ', async () => {
- context.testMocks.getCodeGeneration!.resolves({
- codeGenerationStatus: { status: 'Complete' },
- codeGenerationRemainingIterationCount: 2,
- codeGenerationTotalIterationCount: 3,
- })
-
- context.testMocks.exportResultArchive!.resolves({ newFileContents: [], deletedFiles: [], references: [] })
-
- const testAction = createMockSessionStateAction()
- const state = new FeatureDevCodeGenState(context.testConfig, [], [], [], context.tabId, 0, {}, 2, 3)
- const result = await state.interact(testAction)
-
- const nextState = new FeatureDevPrepareCodeGenState(
- context.testConfig,
- [],
- [],
- [],
- context.tabId,
- 1,
- 2,
- 3,
- undefined
- )
-
- assert.deepStrictEqual(result.nextState?.deletedFiles, nextState.deletedFiles)
- assert.deepStrictEqual(result.nextState?.filePaths, result.nextState?.filePaths)
- assert.deepStrictEqual(result.nextState?.references, result.nextState?.references)
- })
-
- it('fails when codeGenerationStatus failed ', async () => {
- context.testMocks.getCodeGeneration!.rejects(new ToolkitError('Code generation failed'))
- const testAction = createMockSessionStateAction()
- const state = new FeatureDevCodeGenState(context.testConfig, [], [], [], context.tabId, 0, {})
- try {
- await state.interact(testAction)
- assert.fail('failed code generations should throw an error')
- } catch (e: any) {
- assert.deepStrictEqual(e.message, 'Code generation failed')
- }
- })
- })
-})
diff --git a/packages/core/src/test/index.ts b/packages/core/src/test/index.ts
index 9a01973e26d..c682b1f367e 100644
--- a/packages/core/src/test/index.ts
+++ b/packages/core/src/test/index.ts
@@ -22,6 +22,5 @@ export { getTestWorkspaceFolder } from '../testInteg/integrationTestsUtilities'
export * from './codewhisperer/testUtil'
export * from './credentials/testUtil'
export * from './testUtil'
-export * from './amazonq/utils'
export * from './fake/mockFeatureConfigData'
export * from './shared/ui/testUtils'
diff --git a/packages/core/src/testInteg/perf/prepareRepoData.test.ts b/packages/core/src/testInteg/perf/prepareRepoData.test.ts
deleted file mode 100644
index c1ba1df1223..00000000000
--- a/packages/core/src/testInteg/perf/prepareRepoData.test.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import assert from 'assert'
-import * as sinon from 'sinon'
-import { WorkspaceFolder } from 'vscode'
-import { getEqualOSTestOptions, performanceTest } from '../../shared/performance/performance'
-import { createTestWorkspace } from '../../test/testUtil'
-import { prepareRepoData, TelemetryHelper } from '../../amazonqFeatureDev'
-import { AmazonqCreateUpload, fs, getRandomString } from '../../shared'
-import { Span } from '../../shared/telemetry'
-import { FileSystem } from '../../shared/fs/fs'
-import { getFsCallsUpperBound } from './utilities'
-
-type resultType = {
- zipFileBuffer: Buffer
- zipFileChecksum: string
-}
-
-type setupResult = {
- workspace: WorkspaceFolder
- fsSpy: sinon.SinonSpiedInstance
- numFiles: number
- fileSize: number
-}
-
-function performanceTestWrapper(numFiles: number, fileSize: number) {
- return performanceTest(
- getEqualOSTestOptions({
- userCpuUsage: 200,
- systemCpuUsage: 35,
- heapTotal: 20,
- }),
- `handles ${numFiles} files of size ${fileSize} bytes`,
- function () {
- const telemetry = new TelemetryHelper()
- return {
- setup: async () => {
- const fsSpy = sinon.spy(fs)
- const workspace = await createTestWorkspace(numFiles, {
- fileNamePrefix: 'file',
- fileContent: getRandomString(fileSize),
- fileNameSuffix: '.md',
- })
- return { workspace, fsSpy, numFiles, fileSize }
- },
- execute: async (setup: setupResult) => {
- return await prepareRepoData(
- [setup.workspace.uri.fsPath],
- [setup.workspace],
- {
- record: () => {},
- } as unknown as Span,
- { telemetry }
- )
- },
- verify: async (setup: setupResult, result: resultType) => {
- verifyResult(setup, result, telemetry, numFiles * fileSize)
- },
- }
- }
- )
-}
-
-function verifyResult(setup: setupResult, result: resultType, telemetry: TelemetryHelper, expectedSize: number): void {
- assert.ok(result)
- assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true)
- assert.strictEqual(telemetry.repositorySize, expectedSize)
- assert.strictEqual(result.zipFileChecksum.length, 44)
- assert.ok(getFsCallsUpperBound(setup.fsSpy) <= setup.numFiles * 8, 'total system calls should be under 8 per file')
-}
-
-describe('prepareRepoData', function () {
- describe('Performance Tests', function () {
- afterEach(function () {
- sinon.restore()
- })
- performanceTestWrapper(10, 1000)
- performanceTestWrapper(50, 500)
- performanceTestWrapper(100, 100)
- performanceTestWrapper(250, 10)
- })
-})
diff --git a/packages/core/src/testInteg/perf/registerNewFiles.test.ts b/packages/core/src/testInteg/perf/registerNewFiles.test.ts
deleted file mode 100644
index 716e79d4e48..00000000000
--- a/packages/core/src/testInteg/perf/registerNewFiles.test.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import assert from 'assert'
-import sinon from 'sinon'
-import * as vscode from 'vscode'
-import { featureDevScheme } from '../../amazonqFeatureDev'
-import { getEqualOSTestOptions, performanceTest } from '../../shared/performance/performance'
-import { getTestWorkspaceFolder } from '../integrationTestsUtilities'
-import { VirtualFileSystem } from '../../shared'
-import { registerNewFiles } from '../../amazonq/util/files'
-import { NewFileInfo, NewFileZipContents } from '../../amazonq'
-
-interface SetupResult {
- workspace: vscode.WorkspaceFolder
- fileContents: NewFileZipContents[]
- vfsSpy: sinon.SinonSpiedInstance
- vfs: VirtualFileSystem
-}
-
-function getFileContents(numFiles: number, fileSize: number): NewFileZipContents[] {
- return Array.from({ length: numFiles }, (_, i) => {
- return {
- zipFilePath: `test-path-${i}`,
- fileContent: 'x'.repeat(fileSize),
- }
- })
-}
-
-function performanceTestWrapper(label: string, numFiles: number, fileSize: number) {
- const conversationId = 'test-conversation'
- return performanceTest(
- getEqualOSTestOptions({
- userCpuUsage: 300,
- systemCpuUsage: 35,
- heapTotal: 20,
- }),
- label,
- function () {
- return {
- setup: async () => {
- const testWorkspaceUri = vscode.Uri.file(getTestWorkspaceFolder())
- const fileContents = getFileContents(numFiles, fileSize)
- const vfs = new VirtualFileSystem()
- const vfsSpy = sinon.spy(vfs)
-
- return {
- workspace: {
- uri: testWorkspaceUri,
- name: 'test-workspace',
- index: 0,
- },
- fileContents: fileContents,
- vfsSpy: vfsSpy,
- vfs: vfs,
- }
- },
- execute: async (setup: SetupResult) => {
- return registerNewFiles(
- setup.vfs,
- setup.fileContents,
- 'test-upload-id',
- [setup.workspace],
- conversationId,
- featureDevScheme
- )
- },
- verify: async (setup: SetupResult, result: NewFileInfo[]) => {
- assert.strictEqual(result.length, numFiles)
- assert.ok(
- setup.vfsSpy.registerProvider.callCount <= numFiles,
- 'only register each file once in vfs'
- )
- },
- }
- }
- )
-}
-
-describe('registerNewFiles', function () {
- describe('performance tests', function () {
- performanceTestWrapper('1x10MB', 1, 10000)
- performanceTestWrapper('10x1000B', 10, 1000)
- performanceTestWrapper('100x100B', 100, 100)
- performanceTestWrapper('1000x10B', 1000, 10)
- performanceTestWrapper('10000x1B', 10000, 1)
- })
-})
From 0bfc33839a694d2a6da86f19829a9811a044b297 Mon Sep 17 00:00:00 2001
From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com>
Date: Fri, 25 Jul 2025 11:43:26 -0700
Subject: [PATCH 06/18] fix(auth): Fix for SSO Profile Role Chaining Regression
(#7764)
## Github Issue #6902
## Problem
AWS Toolkit version 3.47.0 introduced a regression where profiles using
`source_profile` for role chaining fail to authenticate when the source
profile uses SSO credentials. Users get an "InvalidClientTokenId: The
security token included in the request is invalid" error.
## Root Cause
The issue was introduced in commit 6f6a8c2 (Feb 13, 2025) which
refactored the authentication code to remove deprecated AWS SDK
dependencies. The new implementation in
`makeSharedIniFileCredentialsProvider` method incorrectly assumed that
the source profile would have static credentials (aws_access_key_id and
aws_secret_access_key) directly in the profile data.
When the source profile uses SSO, these static credentials don't exist
in the profile data - they need to be obtained by calling the SSO
service first.
## Solution
The fix modifies the `makeSharedIniFileCredentialsProvider` method in
`packages/core/src/auth/providers/sharedCredentialsProvider.ts` to:
1. Check if the source profile already has resolved credentials (from
`patchSourceCredentials`)
2. If not, create a new `SharedCredentialsProvider` instance for the
source profile and resolve its credentials dynamically
3. Use those resolved credentials to assume the role via STS
This ensures that SSO profiles can be used as source profiles for role
assumption.
## Changed Files
- `packages/core/src/auth/providers/sharedCredentialsProvider.ts` -
Fixed the credential resolution logic
-
`packages/core/src/test/auth/providers/sharedCredentialsProvider.roleChaining.test.ts`
- Added tests to verify the fix
## Testing
The fix includes unit tests that verify:
1. Role chaining from SSO profiles works correctly
2. Role chaining from SSO profiles with MFA works correctly
## Configuration Example
This fix enables configurations like:
```ini
[sso-session aws1_session]
sso_start_url = https://example.awsapps.com/start
sso_region = us-east-1
sso_registration_scopes = sso:account:access
[profile Landing]
sso_session = aws1_session
sso_account_id = 111111111111
sso_role_name = Landing
region = us-east-1
[profile dev]
region = us-east-1
role_arn = arn:aws:iam::123456789012:role/dev
source_profile = Landing
```
Where `dev` profile assumes a role using credentials from the SSO-based
`Landing` profile.
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
.../providers/sharedCredentialsProvider.ts | 26 ++++--
.../sharedCredentialsProvider.test.ts | 79 +++++++++++++++++++
2 files changed, 100 insertions(+), 5 deletions(-)
create mode 100644 packages/core/src/test/auth/providers/sharedCredentialsProvider.test.ts
diff --git a/packages/core/src/auth/providers/sharedCredentialsProvider.ts b/packages/core/src/auth/providers/sharedCredentialsProvider.ts
index 717a151a3af..407db4a717e 100644
--- a/packages/core/src/auth/providers/sharedCredentialsProvider.ts
+++ b/packages/core/src/auth/providers/sharedCredentialsProvider.ts
@@ -406,12 +406,28 @@ export class SharedCredentialsProvider implements CredentialsProvider {
`auth: Profile ${this.profileName} is missing source_profile for role assumption`
)
}
- // Use source profile to assume IAM role based on role ARN provided.
+
+ // Check if we already have resolved credentials from patchSourceCredentials
const sourceProfile = iniData[profile.source_profile!]
- const stsClient = new DefaultStsClient(this.getDefaultRegion() ?? 'us-east-1', {
- accessKeyId: sourceProfile.aws_access_key_id!,
- secretAccessKey: sourceProfile.aws_secret_access_key!,
- })
+ let sourceCredentials: AWS.Credentials
+
+ if (sourceProfile.aws_access_key_id && sourceProfile.aws_secret_access_key) {
+ // Source credentials have already been resolved
+ sourceCredentials = {
+ accessKeyId: sourceProfile.aws_access_key_id,
+ secretAccessKey: sourceProfile.aws_secret_access_key,
+ sessionToken: sourceProfile.aws_session_token,
+ }
+ } else {
+ // Source profile needs credential resolution - this should have been handled by patchSourceCredentials
+ // but if not, we need to resolve it here
+ const sourceProvider = new SharedCredentialsProvider(profile.source_profile!, this.sections)
+ sourceCredentials = await sourceProvider.getCredentials()
+ }
+
+ // Use source credentials to assume IAM role based on role ARN provided.
+ const stsClient = new DefaultStsClient(this.getDefaultRegion() ?? 'us-east-1', sourceCredentials)
+
// Prompt for MFA Token if needed.
const assumeRoleReq = {
RoleArn: profile.role_arn,
diff --git a/packages/core/src/test/auth/providers/sharedCredentialsProvider.test.ts b/packages/core/src/test/auth/providers/sharedCredentialsProvider.test.ts
new file mode 100644
index 00000000000..1884e16e984
--- /dev/null
+++ b/packages/core/src/test/auth/providers/sharedCredentialsProvider.test.ts
@@ -0,0 +1,79 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import assert from 'assert'
+import { SharedCredentialsProvider } from '../../../auth/providers/sharedCredentialsProvider'
+import { createTestSections } from '../../credentials/testUtil'
+import { DefaultStsClient } from '../../../shared/clients/stsClient'
+import { oneDay } from '../../../shared/datetime'
+import sinon from 'sinon'
+import { SsoAccessTokenProvider } from '../../../auth/sso/ssoAccessTokenProvider'
+import { SsoClient } from '../../../auth/sso/clients'
+
+describe('SharedCredentialsProvider - Role Chaining with SSO', function () {
+ let sandbox: sinon.SinonSandbox
+
+ beforeEach(function () {
+ sandbox = sinon.createSandbox()
+ })
+
+ afterEach(function () {
+ sandbox.restore()
+ })
+
+ it('should handle role chaining from SSO profile', async function () {
+ // Mock the SSO authentication
+ sandbox.stub(SsoAccessTokenProvider.prototype, 'getToken').resolves({
+ accessToken: 'test-token',
+ expiresAt: new Date(Date.now() + oneDay),
+ })
+
+ // Mock SSO getRoleCredentials
+ sandbox.stub(SsoClient.prototype, 'getRoleCredentials').resolves({
+ accessKeyId: 'sso-access-key',
+ secretAccessKey: 'sso-secret-key',
+ sessionToken: 'sso-session-token',
+ expiration: new Date(Date.now() + oneDay),
+ })
+
+ // Mock STS assumeRole
+ sandbox.stub(DefaultStsClient.prototype, 'assumeRole').callsFake(async (request) => {
+ assert.strictEqual(request.RoleArn, 'arn:aws:iam::123456789012:role/dev')
+ return {
+ Credentials: {
+ AccessKeyId: 'assumed-access-key',
+ SecretAccessKey: 'assumed-secret-key',
+ SessionToken: 'assumed-session-token',
+ Expiration: new Date(Date.now() + oneDay),
+ },
+ }
+ })
+
+ const sections = await createTestSections(`
+ [sso-session aws1_session]
+ sso_start_url = https://example.awsapps.com/start
+ sso_region = us-east-1
+ sso_registration_scopes = sso:account:access
+
+ [profile Landing]
+ sso_session = aws1_session
+ sso_account_id = 111111111111
+ sso_role_name = Landing
+ region = us-east-1
+
+ [profile dev]
+ region = us-east-1
+ role_arn = arn:aws:iam::123456789012:role/dev
+ source_profile = Landing
+ `)
+
+ const provider = new SharedCredentialsProvider('dev', sections)
+ const credentials = await provider.getCredentials()
+
+ assert.strictEqual(credentials.accessKeyId, 'assumed-access-key')
+ assert.strictEqual(credentials.secretAccessKey, 'assumed-secret-key')
+ assert.strictEqual(credentials.sessionToken, 'assumed-session-token')
+ })
+})
From 6ac1207c792c8d72408159145dffee177039ab12 Mon Sep 17 00:00:00 2001
From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com>
Date: Fri, 25 Jul 2025 12:35:32 -0700
Subject: [PATCH 07/18] config(amazonq): codewhisperer endpoint via
settings.json (#7761)
## Problem
## Solution
allow devs to configure Q endpoint via vscode settings.json
```
"aws.dev.codewhispererService": {
"endpoint": "https://codewhisperer/endpoint/",
"region": "us-east-1"
}
```
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/src/lsp/client.ts | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts
index e56a4a784b6..fa89f5f2ba4 100644
--- a/packages/amazonq/src/lsp/client.ts
+++ b/packages/amazonq/src/lsp/client.ts
@@ -39,6 +39,7 @@ import {
getClientId,
extensionVersion,
isSageMaker,
+ DevSettings,
} from 'aws-core-vscode/shared'
import { processUtils } from 'aws-core-vscode/shared'
import { activate } from './chat/activation'
@@ -129,6 +130,15 @@ export async function startLanguageServer(
await validateNodeExe(executable, resourcePaths.lsp, argv, logger)
+ const endpointOverride = DevSettings.instance.get('codewhispererService', {}).endpoint ?? undefined
+ const textDocSection = {
+ inlineEditSupport: Experiments.instance.get('amazonqLSPNEP', true),
+ } as any
+
+ if (endpointOverride) {
+ textDocSection.endpointOverride = endpointOverride
+ }
+
// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for json documents
@@ -177,9 +187,7 @@ export async function startLanguageServer(
showLogs: true,
},
textDocument: {
- inlineCompletionWithReferences: {
- inlineEditSupport: Experiments.instance.get('amazonqLSPNEP', true),
- },
+ inlineCompletionWithReferences: textDocSection,
},
},
contextConfiguration: {
From f36023f7d49a51d0f6041f38df859b887d9d750b Mon Sep 17 00:00:00 2001
From: Bryce Ito
Date: Mon, 28 Jul 2025 10:35:21 -0700
Subject: [PATCH 08/18] fix(auth): Apply static workspace ID for Eclipse Che
instances (#7614)
## Problem
Eclipse Che-based workspaces on remote compute will change their
hostname if the backing compute changes, thus requiring a reauth.
## Solution
Swap to keying off the Che workspace ID, which should be static for
specific workspaces
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/core/src/shared/vscode/env.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/core/src/shared/vscode/env.ts b/packages/core/src/shared/vscode/env.ts
index 5ee891cc7d3..abd9c58ae2d 100644
--- a/packages/core/src/shared/vscode/env.ts
+++ b/packages/core/src/shared/vscode/env.ts
@@ -307,6 +307,15 @@ export async function getMachineId(): Promise {
// TODO: use `vscode.env.machineId` instead?
return 'browser'
}
+ // Eclipse Che-based envs (backing compute rotates, not classified as a web instance)
+ // TODO: use `vscode.env.machineId` instead?
+ if (process.env.CHE_WORKSPACE_ID) {
+ return process.env.CHE_WORKSPACE_ID
+ }
+ // RedHat Dev Workspaces (run some VSC web variant)
+ if (process.env.DEVWORKSPACE_ID) {
+ return process.env.DEVWORKSPACE_ID
+ }
const proc = new ChildProcess('hostname', [], { collect: true, logging: 'no' })
// TODO: check exit code.
return (await proc.run()).stdout.trim() ?? 'unknown-host'
From 3b852696869d8095f6a250df0cb800e12217a0de Mon Sep 17 00:00:00 2001
From: chungjac
Date: Mon, 28 Jul 2025 11:39:41 -0700
Subject: [PATCH 09/18] telemetry(amazonq): flare is now source of truth for
metrics (#7768)
## Problem
In VSC, we check that the metric name must be in
[aws-toolkit-common](https://github.com/aws/aws-toolkit-common) before
emitting the metric.
Therefore when we want to add a new metric, the current process is:
1. Add new metric in aws-toolkit-common
2. Wait for version to increment (~1 hour)
3. Bump up toolkit-common version in VSC repo
4. Wait for next VSC release (up to 1 week)
Only after steps 1-4, will we be actually emitting the new metric.
JB, VS, and Eclipse do not have this dependency, and assume Flare is the
source of truth for metrics
## Solution
In VSC, Flare is now the source of truth for metrics instead of
depending on aws-toolkit-common
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/src/lsp/chat/messages.ts | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts
index 7b3b130ff85..a95b99b442c 100644
--- a/packages/amazonq/src/lsp/chat/messages.ts
+++ b/packages/amazonq/src/lsp/chat/messages.ts
@@ -97,7 +97,7 @@ import {
ViewDiffMessage,
referenceLogText,
} from 'aws-core-vscode/amazonq'
-import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry'
+import { telemetry } from 'aws-core-vscode/telemetry'
import { isValidResponseError } from './error'
import { decryptResponse, encryptRequest } from '../encryption'
import { getCursorState } from '../utils'
@@ -144,10 +144,13 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie
// This passes through metric data from LSP events to Toolkit telemetry with all fields from the LSP server
languageClient.onTelemetry((e) => {
const telemetryName: string = e.name
-
- if (telemetryName in telemetry) {
- languageClient.info(`[VSCode Telemetry] Emitting ${telemetryName} telemetry: ${JSON.stringify(e.data)}`)
- telemetry[telemetryName as keyof TelemetryBase].emit(e.data)
+ languageClient.info(`[VSCode Telemetry] Emitting ${telemetryName} telemetry: ${JSON.stringify(e.data)}`)
+ try {
+ // Flare is now the source of truth for metrics instead of depending on each IDE client and toolkit-common
+ const metric = (telemetry as any).getMetric(telemetryName)
+ metric?.emit(e.data)
+ } catch (error) {
+ languageClient.warn(`[VSCode Telemetry] Failed to emit ${telemetryName}: ${error}`)
}
})
}
From f724fe9f2904d82aab691302fd2b14e72444f538 Mon Sep 17 00:00:00 2001
From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com>
Date: Mon, 28 Jul 2025 12:30:01 -0700
Subject: [PATCH 10/18] refactor(amazonq): removing agentWalkThrough workflow
(#7775)
## Notes:
- Removing agentWalkThrough workflow form VSCode.
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/package.json | 17 +-
.../amazonq/test/e2e/amazonq/explore.test.ts | 45 ----
packages/core/package.nls.json | 1 -
.../ui/apps/amazonqCommonsConnector.ts | 7 +-
.../core/src/amazonq/webview/ui/connector.ts | 4 -
packages/core/src/amazonq/webview/ui/main.ts | 14 --
.../webview/ui/quickActions/generator.ts | 7 +-
.../webview/ui/storages/tabsStorage.ts | 2 +-
.../src/amazonq/webview/ui/tabs/constants.ts | 2 +-
.../src/amazonq/webview/ui/tabs/generator.ts | 7 +-
.../amazonq/webview/ui/walkthrough/agent.ts | 201 ------------------
packages/core/src/codewhisperer/activation.ts | 2 -
.../codewhisperer/commands/basicCommands.ts | 15 --
13 files changed, 8 insertions(+), 316 deletions(-)
delete mode 100644 packages/amazonq/test/e2e/amazonq/explore.test.ts
delete mode 100644 packages/core/src/amazonq/webview/ui/walkthrough/agent.ts
diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json
index d791ba05af3..58161b6ffdb 100644
--- a/packages/amazonq/package.json
+++ b/packages/amazonq/package.json
@@ -524,22 +524,17 @@
"command": "aws.amazonq.walkthrough.show",
"group": "1_help@1"
},
- {
- "command": "aws.amazonq.exploreAgents",
- "when": "!aws.isSageMaker",
- "group": "1_help@2"
- },
{
"command": "aws.amazonq.github",
- "group": "1_help@3"
+ "group": "1_help@2"
},
{
"command": "aws.amazonq.aboutExtension",
- "group": "1_help@4"
+ "group": "1_help@3"
},
{
"command": "aws.amazonq.viewLogs",
- "group": "1_help@5"
+ "group": "1_help@4"
}
],
"aws.amazonq.submenu.securityIssueMoreActions": [
@@ -846,12 +841,6 @@
"title": "%AWS.amazonq.openChat%",
"category": "%AWS.amazonq.title%"
},
- {
- "command": "aws.amazonq.exploreAgents",
- "title": "%AWS.amazonq.exploreAgents%",
- "category": "%AWS.amazonq.title%",
- "enablement": "aws.codewhisperer.connected && !aws.isSageMaker"
- },
{
"command": "aws.amazonq.walkthrough.show",
"title": "%AWS.amazonq.welcomeWalkthrough%"
diff --git a/packages/amazonq/test/e2e/amazonq/explore.test.ts b/packages/amazonq/test/e2e/amazonq/explore.test.ts
deleted file mode 100644
index 970d93d00bb..00000000000
--- a/packages/amazonq/test/e2e/amazonq/explore.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import assert from 'assert'
-import sinon from 'sinon'
-import { qTestingFramework } from './framework/framework'
-import { Messenger } from './framework/messenger'
-
-describe('Amazon Q Explore page', function () {
- let framework: qTestingFramework
- let tab: Messenger
-
- beforeEach(() => {
- framework = new qTestingFramework('agentWalkthrough', true, [], 0)
- const welcomeTab = framework.getTabs()[0]
- welcomeTab.clickInBodyButton('explore')
-
- // Find the new explore tab
- const exploreTab = framework.findTab('Explore')
- if (!exploreTab) {
- assert.fail('Explore tab not found')
- }
- tab = exploreTab
- })
-
- afterEach(() => {
- framework.removeTab(tab.tabID)
- framework.dispose()
- sinon.restore()
- })
-
- // TODO refactor page objects so we can associate clicking user guides with actual urls
- // TODO test that clicking quick start changes the tab title, etc
- it('should have correct button IDs', async () => {
- const features = ['featuredev', 'testgen', 'doc', 'review', 'gumby']
-
- for (const [index, feature] of features.entries()) {
- const buttons = (tab.getStore().chatItems ?? [])[index].buttons ?? []
- assert.deepStrictEqual(buttons[0].id, `user-guide-${feature}`)
- assert.deepStrictEqual(buttons[1].id, `quick-start-${feature}`)
- }
- })
-})
diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json
index 0a25550ec22..498a3583a00 100644
--- a/packages/core/package.nls.json
+++ b/packages/core/package.nls.json
@@ -354,7 +354,6 @@
"AWS.amazonq.security": "Code Issues",
"AWS.amazonq.login": "Login",
"AWS.amazonq.learnMore": "Learn More About Amazon Q",
- "AWS.amazonq.exploreAgents": "Explore Agent Capabilities",
"AWS.amazonq.welcomeWalkthrough": "Welcome Walkthrough",
"AWS.amazonq.codewhisperer.title": "Amazon Q",
"AWS.amazonq.toggleCodeSuggestion": "Toggle Auto-Suggestions",
diff --git a/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts b/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts
index 68983b6c188..ee20b9b0726 100644
--- a/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts
+++ b/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts
@@ -33,14 +33,12 @@ export interface CodeReference {
export class Connector {
private readonly sendMessageToExtension
private readonly onWelcomeFollowUpClicked
- private readonly onNewTab
private readonly handleCommand
private readonly sendStaticMessage
constructor(props: ConnectorProps) {
this.sendMessageToExtension = props.sendMessageToExtension
this.onWelcomeFollowUpClicked = props.onWelcomeFollowUpClicked
- this.onNewTab = props.onNewTab
this.handleCommand = props.handleCommand
this.sendStaticMessage = props.sendStaticMessages
}
@@ -61,10 +59,7 @@ export class Connector {
}
handleMessageReceive = async (messageData: any): Promise => {
- if (messageData.command === 'showExploreAgentsView') {
- this.onNewTab('agentWalkthrough')
- return
- } else if (messageData.command === 'review') {
+ if (messageData.command === 'review') {
// tabID does not exist when calling from QuickAction Menu bar
this.handleCommand({ command: '/review' }, '')
return
diff --git a/packages/core/src/amazonq/webview/ui/connector.ts b/packages/core/src/amazonq/webview/ui/connector.ts
index cc1b010375a..97821fc842f 100644
--- a/packages/core/src/amazonq/webview/ui/connector.ts
+++ b/packages/core/src/amazonq/webview/ui/connector.ts
@@ -596,10 +596,6 @@ export class Connector {
this.cwChatConnector.onCustomFormAction(tabId, action)
}
break
- case 'agentWalkthrough': {
- this.amazonqCommonsConnector.onCustomFormAction(tabId, action)
- break
- }
}
}
}
diff --git a/packages/core/src/amazonq/webview/ui/main.ts b/packages/core/src/amazonq/webview/ui/main.ts
index 54696982ae0..c6df42f0566 100644
--- a/packages/core/src/amazonq/webview/ui/main.ts
+++ b/packages/core/src/amazonq/webview/ui/main.ts
@@ -32,7 +32,6 @@ import { DiffTreeFileInfo } from './diffTree/types'
import { FeatureContext } from '../../../shared/featureConfig'
import { tryNewMap } from '../../util/functionUtils'
import { welcomeScreenTabData } from './walkthrough/welcome'
-import { agentWalkthroughDataModel } from './walkthrough/agent'
import { createClickTelemetry, createOpenAgentTelemetry } from './telemetry/actions'
import { disclaimerAcknowledgeButtonId, disclaimerCard } from './texts/disclaimer'
import { DetailedListSheetProps } from '@aws/mynah-ui/dist/components/detailed-list/detailed-list-sheet'
@@ -783,19 +782,6 @@ export class WebviewUIHandler {
this.postMessage(createClickTelemetry('amazonq-welcome-quick-start-button'))
return
}
- case 'explore': {
- const newTabId = this.mynahUI?.updateStore('', agentWalkthroughDataModel)
- if (newTabId === undefined) {
- this.mynahUI?.notify({
- content: uiComponentsTexts.noMoreTabsTooltip,
- type: NotificationType.WARNING,
- })
- return
- }
- this.tabsStorage.updateTabTypeFromUnknown(newTabId, 'agentWalkthrough')
- this.postMessage(createClickTelemetry('amazonq-welcome-explore-button'))
- return
- }
default: {
this.connector?.onCustomFormAction(tabId, messageId, action, eventId)
return
diff --git a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts
index 0cc7740f2ec..4ca8b4cc10e 100644
--- a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts
+++ b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts
@@ -25,11 +25,6 @@ export class QuickActionGenerator {
}
public generateForTab(tabType: TabType): QuickActionCommandGroup[] {
- // agentWalkthrough is static and doesn't have any quick actions
- if (tabType === 'agentWalkthrough') {
- return []
- }
-
// TODO: Update acc to UX
const quickActionCommands = [
{
@@ -101,7 +96,7 @@ export class QuickActionGenerator {
].filter((section) => section.commands.length > 0)
const commandUnavailability: Record<
- Exclude,
+ Exclude,
{
description: string
unavailableItems: string[]
diff --git a/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts b/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts
index 92fa7c5a07e..2a803759fd0 100644
--- a/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts
+++ b/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts
@@ -4,7 +4,7 @@
*/
export type TabStatus = 'free' | 'busy' | 'dead'
-const TabTypes = ['cwc', 'gumby', 'review', 'agentWalkthrough', 'welcome', 'unknown'] as const
+const TabTypes = ['cwc', 'gumby', 'review', 'welcome', 'unknown'] as const
export type TabType = (typeof TabTypes)[number]
export function isTabType(value: string): value is TabType {
return (TabTypes as readonly string[]).includes(value)
diff --git a/packages/core/src/amazonq/webview/ui/tabs/constants.ts b/packages/core/src/amazonq/webview/ui/tabs/constants.ts
index ead70679b7f..0872b829a6a 100644
--- a/packages/core/src/amazonq/webview/ui/tabs/constants.ts
+++ b/packages/core/src/amazonq/webview/ui/tabs/constants.ts
@@ -44,7 +44,7 @@ export const commonTabData: TabTypeData = {
contextCommands: [workspaceCommand],
}
-export const TabTypeDataMap: Record, TabTypeData> = {
+export const TabTypeDataMap: Record, TabTypeData> = {
unknown: commonTabData,
cwc: commonTabData,
gumby: {
diff --git a/packages/core/src/amazonq/webview/ui/tabs/generator.ts b/packages/core/src/amazonq/webview/ui/tabs/generator.ts
index 2331a0721c7..68b758d51cb 100644
--- a/packages/core/src/amazonq/webview/ui/tabs/generator.ts
+++ b/packages/core/src/amazonq/webview/ui/tabs/generator.ts
@@ -8,7 +8,6 @@ import { TabType } from '../storages/tabsStorage'
import { FollowUpGenerator } from '../followUps/generator'
import { QuickActionGenerator } from '../quickActions/generator'
import { qChatIntroMessageForSMUS, TabTypeDataMap } from './constants'
-import { agentWalkthroughDataModel } from '../walkthrough/agent'
import { FeatureContext } from '../../../../shared/featureConfig'
import { RegionProfile } from '../../../../codewhisperer/models/model'
@@ -43,10 +42,6 @@ export class TabDataGenerator {
taskName?: string,
isSMUS?: boolean
): MynahUIDataModel {
- if (tabType === 'agentWalkthrough') {
- return agentWalkthroughDataModel
- }
-
if (tabType === 'welcome') {
return {}
}
@@ -86,7 +81,7 @@ export class TabDataGenerator {
}
private getContextCommands(tabType: TabType): QuickActionCommandGroup[] | undefined {
- if (tabType === 'agentWalkthrough' || tabType === 'welcome') {
+ if (tabType === 'welcome') {
return
}
diff --git a/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts b/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts
deleted file mode 100644
index f4a5add7aa1..00000000000
--- a/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts
+++ /dev/null
@@ -1,201 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ChatItemContent, ChatItemType, MynahIcons, MynahUIDataModel } from '@aws/mynah-ui'
-
-function createdTabbedData(examples: string[], agent: string): ChatItemContent['tabbedContent'] {
- const exampleText = examples.map((example) => `- ${example}`).join('\n')
- return [
- {
- label: 'Examples',
- value: 'examples',
- content: {
- body: `**Example use cases:**\n${exampleText}\n\nEnter ${agent} in Q Chat to get started`,
- },
- },
- ]
-}
-
-export const agentWalkthroughDataModel: MynahUIDataModel = {
- tabBackground: false,
- compactMode: false,
- tabTitle: 'Explore',
- promptInputVisible: false,
- tabHeaderDetails: {
- icon: MynahIcons.ASTERISK,
- title: 'Amazon Q Developer agents capabilities',
- description: '',
- },
- chatItems: [
- {
- type: ChatItemType.ANSWER,
- snapToTop: true,
- hoverEffect: true,
- body: `### Feature development
-Implement features or make changes across your workspace, all from a single prompt.
-`,
- icon: MynahIcons.CODE_BLOCK,
- footer: {
- tabbedContent: createdTabbedData(
- [
- '/dev update app.py to add a new api',
- '/dev fix the error',
- '/dev add a new button to sort by ',
- ],
- '/dev'
- ),
- },
- buttons: [
- {
- status: 'clear',
- id: `user-guide-featuredev`,
- disabled: false,
- text: 'Read user guide',
- },
- {
- status: 'main',
- disabled: false,
- flash: 'once',
- fillState: 'hover',
- icon: MynahIcons.RIGHT_OPEN,
- id: 'quick-start-featuredev',
- text: `Quick start with **/dev**`,
- },
- ],
- },
- {
- type: ChatItemType.ANSWER,
- hoverEffect: true,
- body: `### Unit test generation
-Automatically generate unit tests for your active file.
-`,
- icon: MynahIcons.BUG,
- footer: {
- tabbedContent: createdTabbedData(
- ['Generate tests for specific functions', 'Generate tests for null and empty inputs'],
- '/test'
- ),
- },
- buttons: [
- {
- status: 'clear',
- id: 'user-guide-testgen',
- disabled: false,
- text: 'Read user guide',
- },
- {
- status: 'main',
- disabled: false,
- flash: 'once',
- fillState: 'hover',
- icon: MynahIcons.RIGHT_OPEN,
- id: 'quick-start-testgen',
- text: `Quick start with **/test**`,
- },
- ],
- },
- {
- type: ChatItemType.ANSWER,
- hoverEffect: true,
- body: `### Documentation generation
-Create and update READMEs for better documented code.
-`,
- icon: MynahIcons.CHECK_LIST,
- footer: {
- tabbedContent: createdTabbedData(
- [
- 'Generate new READMEs for your project',
- 'Update existing READMEs with recent code changes',
- 'Request specific changes to a README',
- ],
- '/doc'
- ),
- },
- buttons: [
- {
- status: 'clear',
- id: 'user-guide-doc',
- disabled: false,
- text: 'Read user guide',
- },
- {
- status: 'main',
- disabled: false,
- flash: 'once',
- fillState: 'hover',
- icon: MynahIcons.RIGHT_OPEN,
- id: 'quick-start-doc',
- text: `Quick start with **/doc**`,
- },
- ],
- },
- {
- type: ChatItemType.ANSWER,
- hoverEffect: true,
- body: `### Code reviews
-Review code for issues, then get suggestions to fix your code instantaneously.
-`,
- icon: MynahIcons.TRANSFORM,
- footer: {
- tabbedContent: createdTabbedData(
- [
- 'Review code for security vulnerabilities and code quality issues',
- 'Get detailed explanations about code issues',
- 'Apply automatic code fixes to your files',
- ],
- '/review'
- ),
- },
- buttons: [
- {
- status: 'clear',
- id: 'user-guide-review',
- disabled: false,
- text: 'Read user guide',
- },
- {
- status: 'main',
- disabled: false,
- flash: 'once',
- fillState: 'hover',
- icon: MynahIcons.RIGHT_OPEN,
- id: 'quick-start-review',
- text: `Quick start with **/review**`,
- },
- ],
- },
- {
- type: ChatItemType.ANSWER,
- hoverEffect: true,
- body: `### Transformation
-Upgrade library and language versions in your codebase.
-`,
- icon: MynahIcons.TRANSFORM,
- footer: {
- tabbedContent: createdTabbedData(
- ['Upgrade Java language and dependency versions', 'Convert embedded SQL code in Java apps'],
- '/transform'
- ),
- },
- buttons: [
- {
- status: 'clear',
- id: 'user-guide-gumby',
- disabled: false,
- text: 'Read user guide',
- },
- {
- status: 'main',
- disabled: false,
- flash: 'once',
- fillState: 'hover',
- icon: MynahIcons.RIGHT_OPEN,
- id: 'quick-start-gumby',
- text: `Quick start with **/transform**`,
- },
- ],
- },
- ],
-}
diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts
index 1e73b640a1e..941156a0d2e 100644
--- a/packages/core/src/codewhisperer/activation.ts
+++ b/packages/core/src/codewhisperer/activation.ts
@@ -49,7 +49,6 @@ import {
regenerateFix,
ignoreAllIssues,
focusIssue,
- showExploreAgentsView,
showCodeIssueGroupingQuickPick,
selectRegionProfileCommand,
} from './commands/basicCommands'
@@ -301,7 +300,6 @@ export async function activate(context: ExtContext): Promise {
vscode.window.registerWebviewViewProvider(ReferenceLogViewProvider.viewType, ReferenceLogViewProvider.instance),
showReferenceLog.register(),
showLogs.register(),
- showExploreAgentsView.register(),
vscode.languages.registerCodeLensProvider(
[...CodeWhispererConstants.platformLanguageIds],
ReferenceInlineProvider.instance
diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts
index a8c21b86ce2..ec1b5ae6e63 100644
--- a/packages/core/src/codewhisperer/commands/basicCommands.ts
+++ b/packages/core/src/codewhisperer/commands/basicCommands.ts
@@ -60,7 +60,6 @@ import { SecurityIssueProvider } from '../service/securityIssueProvider'
import { CodeWhispererSettings } from '../util/codewhispererSettings'
import { closeDiff, getPatchedCode } from '../../shared/utilities/diffUtils'
import { insertCommentAboveLine } from '../../shared/utilities/commentUtils'
-import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext'
import path from 'path'
import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker'
import { parsePatch } from 'diff'
@@ -173,20 +172,6 @@ export const showLogs = Commands.declare(
}
)
-export const showExploreAgentsView = Commands.declare(
- { id: 'aws.amazonq.exploreAgents', compositeKey: { 1: 'source' } },
- () => async (_: VsCodeCommandArg, source: CodeWhispererSource) => {
- if (_ !== placeholder) {
- source = 'ellipsesMenu'
- }
-
- DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({
- sender: 'amazonqCore',
- command: 'showExploreAgentsView',
- })
- }
-)
-
export const showIntroduction = Commands.declare('aws.amazonq.introduction', () => async () => {
void openUrl(vscode.Uri.parse(CodeWhispererConstants.learnMoreUriGeneral))
})
From 0fcd624a9c7b32d79f1b771a2bc8d3d66f26db67 Mon Sep 17 00:00:00 2001
From: abhraina-aws
Date: Mon, 28 Jul 2025 13:24:57 -0700
Subject: [PATCH 11/18] fix(amazonq): switch off the feature flag incase
sagemaker is involved (#7777)
## Problem
Sagemaker was showing show logs feature when it cant support it.
## Solution
Added the check for sage maker.
---
- Treat all work as PUBLIC. Private `feature/x` branches will not be
squash-merged at release time.
- Your code changes must meet the guidelines in
[CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines).
- License: I confirm that my contribution is made under the terms of the
Apache 2.0 license.
---
packages/amazonq/src/lsp/client.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts
index fa89f5f2ba4..d335dae40ef 100644
--- a/packages/amazonq/src/lsp/client.ts
+++ b/packages/amazonq/src/lsp/client.ts
@@ -184,7 +184,7 @@ export async function startLanguageServer(
window: {
notifications: true,
showSaveFileDialog: true,
- showLogs: true,
+ showLogs: isSageMaker() ? false : true,
},
textDocument: {
inlineCompletionWithReferences: textDocSection,
From 0dd5bf437bb18cdfa3a12a0aea2f282acd2ad864 Mon Sep 17 00:00:00 2001
From: Aidan Ton
Date: Mon, 28 Jul 2025 15:56:49 -0700
Subject: [PATCH 12/18] fix(amazonq): skip EDITS suggestion if there is no
change between current and new code suggestion
---
.../app/inline/EditRendering/imageRenderer.ts | 8 ++--
.../app/inline/EditRendering/svgGenerator.ts | 47 ++++++++++++++-----
.../EditRendering/imageRenderer.test.ts | 4 +-
3 files changed, 43 insertions(+), 16 deletions(-)
diff --git a/packages/amazonq/src/app/inline/EditRendering/imageRenderer.ts b/packages/amazonq/src/app/inline/EditRendering/imageRenderer.ts
index 195879ff779..5f204343b5e 100644
--- a/packages/amazonq/src/app/inline/EditRendering/imageRenderer.ts
+++ b/packages/amazonq/src/app/inline/EditRendering/imageRenderer.ts
@@ -26,8 +26,10 @@ export async function showEdits(
const svgGenerationService = new SvgGenerationService()
// Generate your SVG image with the file contents
const currentFile = editor.document.uri.fsPath
- const { svgImage, startLine, newCode, origionalCodeHighlightRange } =
- await svgGenerationService.generateDiffSvg(currentFile, item.insertText as string)
+ const { svgImage, startLine, newCode, originalCodeHighlightRange } = await svgGenerationService.generateDiffSvg(
+ currentFile,
+ item.insertText as string
+ )
// TODO: To investigate why it fails and patch [generateDiffSvg]
if (newCode.length === 0) {
@@ -42,7 +44,7 @@ export async function showEdits(
svgImage,
startLine,
newCode,
- origionalCodeHighlightRange,
+ originalCodeHighlightRange,
session,
languageClient,
item,
diff --git a/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts b/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
index 45a615e318e..7be9ecf87f2 100644
--- a/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
+++ b/packages/amazonq/src/app/inline/EditRendering/svgGenerator.ts
@@ -11,6 +11,12 @@ type Range = { line: number; start: number; end: number }
const logger = getLogger('nextEditPrediction')
export const imageVerticalOffset = 1
+export const emptyDiffSvg = {
+ svgImage: vscode.Uri.parse(''),
+ startLine: 0,
+ newCode: '',
+ originalCodeHighlightRange: [],
+}
export class SvgGenerationService {
/**
@@ -27,7 +33,7 @@ export class SvgGenerationService {
svgImage: vscode.Uri
startLine: number
newCode: string
- origionalCodeHighlightRange: Range[]
+ originalCodeHighlightRange: Range[]
}> {
const textDoc = await vscode.workspace.openTextDocument(filePath)
const originalCode = textDoc.getText().replaceAll('\r\n', '\n')
@@ -52,6 +58,21 @@ export class SvgGenerationService {
// Get edit diffs with highlight
const { addedLines, removedLines } = this.getEditedLinesFromDiff(udiff)
+
+ // Calculate dimensions based on code content
+ const { offset, editStartLine, isPositionValid } = this.calculatePosition(
+ originalCode.split('\n'),
+ newCode.split('\n'),
+ addedLines,
+ currentTheme
+ )
+
+ // if the position for the EDITS suggestion is not valid (there is no difference between new
+ // and current code content), return EMPTY_DIFF_SVG and skip the suggestion.
+ if (!isPositionValid) {
+ return emptyDiffSvg
+ }
+
const highlightRanges = this.generateHighlightRanges(removedLines, addedLines, modifiedLines)
const diffAddedWithHighlight = this.getHighlightEdit(addedLines, highlightRanges.addedRanges)
@@ -61,13 +82,6 @@ export class SvgGenerationService {
registerWindow(window, document)
const draw = SVG(document.documentElement) as any
- // Calculate dimensions based on code content
- const { offset, editStartLine } = this.calculatePosition(
- originalCode.split('\n'),
- newCode.split('\n'),
- addedLines,
- currentTheme
- )
const { width, height } = this.calculateDimensions(addedLines, currentTheme)
draw.size(width + offset, height)
@@ -86,7 +100,7 @@ export class SvgGenerationService {
svgImage: vscode.Uri.parse(svgResult),
startLine: editStartLine,
newCode: newCode,
- origionalCodeHighlightRange: highlightRanges.removedRanges,
+ originalCodeHighlightRange: highlightRanges.removedRanges,
}
}
@@ -356,12 +370,23 @@ export class SvgGenerationService {
newLines: string[],
diffLines: string[],
theme: editorThemeInfo
- ): { offset: number; editStartLine: number } {
+ ): { offset: number; editStartLine: number; isPositionValid: boolean } {
// Determine the starting line of the edit in the original file
let editStartLineInOldFile = 0
const maxLength = Math.min(originalLines.length, newLines.length)
for (let i = 0; i <= maxLength; i++) {
+ // if there is no difference between the original lines and the new lines, skip calculating for the start position.
+ if (i === maxLength && originalLines[i] === newLines[i] && originalLines.length === newLines.length) {
+ logger.info(
+ 'There is no difference between current and new code suggestion. Skip calculating for start position.'
+ )
+ return {
+ offset: 0,
+ editStartLine: 0,
+ isPositionValid: false,
+ }
+ }
if (originalLines[i] !== newLines[i] || i === maxLength) {
editStartLineInOldFile = i
break
@@ -386,7 +411,7 @@ export class SvgGenerationService {
const startLineLength = originalLines[startLine]?.length || 0
const offset = (maxLineLength - startLineLength) * theme.fontSize * 0.7 + 10 // padding
- return { offset, editStartLine: editStartLineInOldFile }
+ return { offset, editStartLine: editStartLineInOldFile, isPositionValid: true }
}
private escapeHtml(text: string): string {
diff --git a/packages/amazonq/test/unit/app/inline/EditRendering/imageRenderer.test.ts b/packages/amazonq/test/unit/app/inline/EditRendering/imageRenderer.test.ts
index 8a625fe3544..e1c32778d83 100644
--- a/packages/amazonq/test/unit/app/inline/EditRendering/imageRenderer.test.ts
+++ b/packages/amazonq/test/unit/app/inline/EditRendering/imageRenderer.test.ts
@@ -30,7 +30,7 @@ describe('showEdits', function () {
svgImage: vscode.Uri.file('/path/to/generated.svg'),
startLine: 5,
newCode: 'console.log("Hello World");',
- origionalCodeHighlightRange: [{ line: 5, start: 0, end: 10 }],
+ originalCodeHighlightRange: [{ line: 5, start: 0, end: 10 }],
...overrides,
}
}
@@ -167,7 +167,7 @@ describe('showEdits', function () {
mockSvgResult.svgImage,
mockSvgResult.startLine,
mockSvgResult.newCode,
- mockSvgResult.origionalCodeHighlightRange,
+ mockSvgResult.originalCodeHighlightRange,
sessionStub,
languageClientStub,
itemStub
From 8e11197d09918717dc0faf8bdfec0ee2ef95b3d8 Mon Sep 17 00:00:00 2001
From: abhraina-aws
Date: Mon, 28 Jul 2025 18:01:18 -0700
Subject: [PATCH 13/18] fix(amazonq): update the marketing message for the
Amazon Q plugin
---
packages/amazonq/README.md | 34 +++++++++++++++-------------------
packages/amazonq/package.json | 2 +-
2 files changed, 16 insertions(+), 20 deletions(-)
diff --git a/packages/amazonq/README.md b/packages/amazonq/README.md
index 46091a98d10..e3ec16bb2ac 100644
--- a/packages/amazonq/README.md
+++ b/packages/amazonq/README.md
@@ -3,39 +3,33 @@
[](https://www.youtube.com/@amazonwebservices)

-# Agent capabilities
+# Agentic coding experience
+
+Amazon Q Developer uses information across native and MCP server-based tools to intelligently perform actions beyond code suggestions, such as reading files, generating code diffs, and running commands based on your natural language instruction. Simply type your prompt in your preferred language and Q Developer will provide continuous status updates and iteratively apply changes based on your feedback, helping you accomplish tasks faster.
### Implement new features
-`/dev` to task Amazon Q with generating new code across your entire project and implement features.
+Generate new code across your entire project and implement features.
### Generate documentation
-`/doc` to task Amazon Q with writing API, technical design, and onboarding documentation.
+Write API, technical design, and onboarding documentation.
### Automate code reviews
-`/review` to ask Amazon Q to perform code reviews, flagging suspicious code patterns and assessing deployment risk.
+Perform code reviews, flagging suspicious code patterns and assessing deployment risk.
### Generate unit tests
-`/test` to ask Amazon Q to generate unit tests and add them to your project, helping you improve code quality, fast.
-
-### Transform workloads
-
-`/transform` to upgrade your Java applications in minutes, not weeks.
+Generate unit tests and add them to your project, helping you improve code quality, fast.
# Core features
-### Inline chat
-
-Seamlessly initiate chat within the inline coding experience. Select a section of code that you need assistance with and initiate chat within the editor to request actions such as "Optimize this code", "Add comments", or "Write tests".
-
-### Chat
+### MCP support
-Generate code, explain code, and get answers about software development.
+Add Model Context Protocol (MCP) servers to give Amazon Q Developer access to important context.
### Inline suggestions
@@ -43,9 +37,13 @@ Receive real-time code suggestions ranging from snippets to full functions based
[_15+ languages supported including Python, TypeScript, Rust, Terraform, AWS Cloudformation, and more_](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html)
-### Code reference log
+### Inline chat
+
+Seamlessly chat within the inline coding experience. Select a section of code that you need assistance with and initiate chat within the editor to request actions such as "Optimize this code", "Add comments", or "Write tests".
-Attribute code from Amazon Q that is similar to training data. When code suggestions similar to training data are accepted, they will be added to the code reference log.
+### Chat
+
+Generate code, explain code, and get answers about software development.
@@ -55,8 +53,6 @@ Attribute code from Amazon Q that is similar to training data. When code suggest
**Pro Tier** - if your organization is on the Amazon Q Developer Pro tier, log in with single sign-on.
-
-
# Troubleshooting & feedback
[File a bug](https://github.com/aws/aws-toolkit-vscode/issues/new?assignees=&labels=bug&projects=&template=bug_report.md) or [submit a feature request](https://github.com/aws/aws-toolkit-vscode/issues/new?assignees=&labels=feature-request&projects=&template=feature_request.md) on our Github repository.
diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json
index 58161b6ffdb..97c9ca60d19 100644
--- a/packages/amazonq/package.json
+++ b/packages/amazonq/package.json
@@ -1,7 +1,7 @@
{
"name": "amazon-q-vscode",
"displayName": "Amazon Q",
- "description": "The most capable generative AI-powered assistant for building, operating, and transforming software, with advanced capabilities for managing data and AI",
+ "description": "The most capable generative AI–powered assistant for software development.",
"version": "1.86.0-SNAPSHOT",
"extensionKind": [
"workspace"
From 7741988c7aa76c5c4d5b503a665808065333e068 Mon Sep 17 00:00:00 2001
From: aws-toolkit-automation <>
Date: Wed, 30 Jul 2025 21:07:50 +0000
Subject: [PATCH 14/18] Release 1.86.0
---
package-lock.json | 4 ++--
packages/amazonq/.changes/1.86.0.json | 18 ++++++++++++++++++
...x-316fb610-0ea9-40d1-bdb7-d371a6be4a4e.json | 4 ----
...x-7261a487-e80a-440f-b311-2688e256a886.json | 4 ----
...x-9d694e40-7fc7-4504-b08c-6b22a5ebcb1c.json | 4 ----
packages/amazonq/CHANGELOG.md | 6 ++++++
packages/amazonq/package.json | 2 +-
7 files changed, 27 insertions(+), 15 deletions(-)
create mode 100644 packages/amazonq/.changes/1.86.0.json
delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-316fb610-0ea9-40d1-bdb7-d371a6be4a4e.json
delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-9d694e40-7fc7-4504-b08c-6b22a5ebcb1c.json
diff --git a/package-lock.json b/package-lock.json
index 84225f223d9..c70ce4293bf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,7 +46,7 @@
"prettier": "^3.3.3",
"prettier-plugin-sh": "^0.14.0",
"pretty-quick": "^4.0.0",
- "ts-node": "^10.9.1",
+ "ts-node": "^10.9.2",
"typescript": "^5.0.4",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
@@ -29954,7 +29954,7 @@
},
"packages/amazonq": {
"name": "amazon-q-vscode",
- "version": "1.86.0-SNAPSHOT",
+ "version": "1.86.0",
"license": "Apache-2.0",
"dependencies": {
"aws-core-vscode": "file:../core/"
diff --git a/packages/amazonq/.changes/1.86.0.json b/packages/amazonq/.changes/1.86.0.json
new file mode 100644
index 00000000000..abe84ce5b5f
--- /dev/null
+++ b/packages/amazonq/.changes/1.86.0.json
@@ -0,0 +1,18 @@
+{
+ "date": "2025-07-30",
+ "version": "1.86.0",
+ "entries": [
+ {
+ "type": "Bug Fix",
+ "description": "Let Enter invoke auto completion more consistently"
+ },
+ {
+ "type": "Bug Fix",
+ "description": "Faster and more responsive inline completion UX"
+ },
+ {
+ "type": "Bug Fix",
+ "description": "Use documentChangeEvent as auto trigger condition"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/amazonq/.changes/next-release/Bug Fix-316fb610-0ea9-40d1-bdb7-d371a6be4a4e.json b/packages/amazonq/.changes/next-release/Bug Fix-316fb610-0ea9-40d1-bdb7-d371a6be4a4e.json
deleted file mode 100644
index 1a9e5c32e6d..00000000000
--- a/packages/amazonq/.changes/next-release/Bug Fix-316fb610-0ea9-40d1-bdb7-d371a6be4a4e.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "type": "Bug Fix",
- "description": "Let Enter invoke auto completion more consistently"
-}
diff --git a/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json b/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
deleted file mode 100644
index 29d129cc287..00000000000
--- a/packages/amazonq/.changes/next-release/Bug Fix-7261a487-e80a-440f-b311-2688e256a886.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "type": "Bug Fix",
- "description": "Faster and more responsive inline completion UX"
-}
diff --git a/packages/amazonq/.changes/next-release/Bug Fix-9d694e40-7fc7-4504-b08c-6b22a5ebcb1c.json b/packages/amazonq/.changes/next-release/Bug Fix-9d694e40-7fc7-4504-b08c-6b22a5ebcb1c.json
deleted file mode 100644
index f2234549a0d..00000000000
--- a/packages/amazonq/.changes/next-release/Bug Fix-9d694e40-7fc7-4504-b08c-6b22a5ebcb1c.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "type": "Bug Fix",
- "description": "Use documentChangeEvent as auto trigger condition"
-}
diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md
index d96b350db8d..1f21731331e 100644
--- a/packages/amazonq/CHANGELOG.md
+++ b/packages/amazonq/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 1.86.0 2025-07-30
+
+- **Bug Fix** Let Enter invoke auto completion more consistently
+- **Bug Fix** Faster and more responsive inline completion UX
+- **Bug Fix** Use documentChangeEvent as auto trigger condition
+
## 1.85.0 2025-07-19
- Miscellaneous non-user-facing changes
diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json
index 97c9ca60d19..1c27f6f72b9 100644
--- a/packages/amazonq/package.json
+++ b/packages/amazonq/package.json
@@ -2,7 +2,7 @@
"name": "amazon-q-vscode",
"displayName": "Amazon Q",
"description": "The most capable generative AI–powered assistant for software development.",
- "version": "1.86.0-SNAPSHOT",
+ "version": "1.86.0",
"extensionKind": [
"workspace"
],
From 11ca7cefa4b16da29d4717a6c2f31374b4464e4c Mon Sep 17 00:00:00 2001
From: aws-toolkit-automation <>
Date: Wed, 30 Jul 2025 21:11:38 +0000
Subject: [PATCH 15/18] Release 3.70.0
---
package-lock.json | 4 ++--
packages/toolkit/.changes/3.70.0.json | 10 ++++++++++
.../Feature-852d6637-ac3c-4b7f-b846-649d23da87e3.json | 4 ----
packages/toolkit/CHANGELOG.md | 4 ++++
packages/toolkit/package.json | 2 +-
5 files changed, 17 insertions(+), 7 deletions(-)
create mode 100644 packages/toolkit/.changes/3.70.0.json
delete mode 100644 packages/toolkit/.changes/next-release/Feature-852d6637-ac3c-4b7f-b846-649d23da87e3.json
diff --git a/package-lock.json b/package-lock.json
index 84225f223d9..4370a6caad1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,7 +46,7 @@
"prettier": "^3.3.3",
"prettier-plugin-sh": "^0.14.0",
"pretty-quick": "^4.0.0",
- "ts-node": "^10.9.1",
+ "ts-node": "^10.9.2",
"typescript": "^5.0.4",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
@@ -31678,7 +31678,7 @@
},
"packages/toolkit": {
"name": "aws-toolkit-vscode",
- "version": "3.70.0-SNAPSHOT",
+ "version": "3.70.0",
"license": "Apache-2.0",
"dependencies": {
"aws-core-vscode": "file:../core/"
diff --git a/packages/toolkit/.changes/3.70.0.json b/packages/toolkit/.changes/3.70.0.json
new file mode 100644
index 00000000000..a41386724ab
--- /dev/null
+++ b/packages/toolkit/.changes/3.70.0.json
@@ -0,0 +1,10 @@
+{
+ "date": "2025-07-30",
+ "version": "3.70.0",
+ "entries": [
+ {
+ "type": "Feature",
+ "description": "Improved connection actions for SSO"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/toolkit/.changes/next-release/Feature-852d6637-ac3c-4b7f-b846-649d23da87e3.json b/packages/toolkit/.changes/next-release/Feature-852d6637-ac3c-4b7f-b846-649d23da87e3.json
deleted file mode 100644
index 2d7652be636..00000000000
--- a/packages/toolkit/.changes/next-release/Feature-852d6637-ac3c-4b7f-b846-649d23da87e3.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "type": "Feature",
- "description": "Improved connection actions for SSO"
-}
diff --git a/packages/toolkit/CHANGELOG.md b/packages/toolkit/CHANGELOG.md
index 83cc14ff4e7..8d1ba6894c6 100644
--- a/packages/toolkit/CHANGELOG.md
+++ b/packages/toolkit/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.70.0 2025-07-30
+
+- **Feature** Improved connection actions for SSO
+
## 3.69.0 2025-07-16
- **Bug Fix** SageMaker: Enable per-region manual filtering of Spaces
diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json
index 34fb02a8bd6..305a82a4cfb 100644
--- a/packages/toolkit/package.json
+++ b/packages/toolkit/package.json
@@ -2,7 +2,7 @@
"name": "aws-toolkit-vscode",
"displayName": "AWS Toolkit",
"description": "Including CodeCatalyst, Infrastructure Composer, and support for Lambda, S3, CloudWatch Logs, CloudFormation, and many other services.",
- "version": "3.70.0-SNAPSHOT",
+ "version": "3.70.0",
"extensionKind": [
"workspace"
],
From e3f447d19f39b628737fe867e8c1f909c91d88d4 Mon Sep 17 00:00:00 2001
From: aws-toolkit-automation <>
Date: Wed, 30 Jul 2025 22:02:41 +0000
Subject: [PATCH 16/18] Update version to snapshot version: 1.87.0-SNAPSHOT
---
package-lock.json | 4 ++--
packages/amazonq/package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index c70ce4293bf..0f2147807c4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,7 +46,7 @@
"prettier": "^3.3.3",
"prettier-plugin-sh": "^0.14.0",
"pretty-quick": "^4.0.0",
- "ts-node": "^10.9.2",
+ "ts-node": "^10.9.1",
"typescript": "^5.0.4",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
@@ -29954,7 +29954,7 @@
},
"packages/amazonq": {
"name": "amazon-q-vscode",
- "version": "1.86.0",
+ "version": "1.87.0-SNAPSHOT",
"license": "Apache-2.0",
"dependencies": {
"aws-core-vscode": "file:../core/"
diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json
index 1c27f6f72b9..88a246b9a74 100644
--- a/packages/amazonq/package.json
+++ b/packages/amazonq/package.json
@@ -2,7 +2,7 @@
"name": "amazon-q-vscode",
"displayName": "Amazon Q",
"description": "The most capable generative AI–powered assistant for software development.",
- "version": "1.86.0",
+ "version": "1.87.0-SNAPSHOT",
"extensionKind": [
"workspace"
],
From aa2c6e74d3b69f06351372e04ed48a063e4e1e3f Mon Sep 17 00:00:00 2001
From: aws-toolkit-automation <>
Date: Wed, 30 Jul 2025 22:10:08 +0000
Subject: [PATCH 17/18] Update version to snapshot version: 3.71.0-SNAPSHOT
---
package-lock.json | 4 ++--
packages/toolkit/package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 4370a6caad1..90204398d0b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -46,7 +46,7 @@
"prettier": "^3.3.3",
"prettier-plugin-sh": "^0.14.0",
"pretty-quick": "^4.0.0",
- "ts-node": "^10.9.2",
+ "ts-node": "^10.9.1",
"typescript": "^5.0.4",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
@@ -31678,7 +31678,7 @@
},
"packages/toolkit": {
"name": "aws-toolkit-vscode",
- "version": "3.70.0",
+ "version": "3.71.0-SNAPSHOT",
"license": "Apache-2.0",
"dependencies": {
"aws-core-vscode": "file:../core/"
diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json
index 305a82a4cfb..b4500fa9529 100644
--- a/packages/toolkit/package.json
+++ b/packages/toolkit/package.json
@@ -2,7 +2,7 @@
"name": "aws-toolkit-vscode",
"displayName": "AWS Toolkit",
"description": "Including CodeCatalyst, Infrastructure Composer, and support for Lambda, S3, CloudWatch Logs, CloudFormation, and many other services.",
- "version": "3.70.0",
+ "version": "3.71.0-SNAPSHOT",
"extensionKind": [
"workspace"
],
From 632a570490c52a12febbca788a1178ccfe8261e6 Mon Sep 17 00:00:00 2001
From: BlakeLazarine
Date: Wed, 30 Jul 2025 15:43:16 -0700
Subject: [PATCH 18/18] Merge pull request #7739 from singhAws/code-review-tool
feat(amazonq): adapt feature flag for CodeReviewInChat, emit metrics for explainIssue, applyFix
---
packages/amazonq/src/app/amazonqScan/app.ts | 21 ++++++++------
.../src/app/amazonqScan/models/constants.ts | 2 ++
packages/amazonq/src/lsp/chat/commands.ts | 28 ++++++++++++++++---
packages/amazonq/src/lsp/client.ts | 3 +-
4 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/packages/amazonq/src/app/amazonqScan/app.ts b/packages/amazonq/src/app/amazonqScan/app.ts
index 21857163bd2..2b237ab534e 100644
--- a/packages/amazonq/src/app/amazonqScan/app.ts
+++ b/packages/amazonq/src/app/amazonqScan/app.ts
@@ -19,6 +19,7 @@ import { Messenger } from './chat/controller/messenger/messenger'
import { UIMessageListener } from './chat/views/actions/uiMessageListener'
import { debounce } from 'lodash'
import { Commands, placeholder } from 'aws-core-vscode/shared'
+import { codeReviewInChat } from './models/constants'
export function init(appContext: AmazonQAppInitContext) {
const scanChatControllerEventEmitters: ScanChatControllerEventEmitters = {
@@ -74,17 +75,19 @@ export function init(appContext: AmazonQAppInitContext) {
return debouncedEvent()
})
- Commands.register('aws.amazonq.security.scan-statusbar', async () => {
- if (AuthUtil.instance.isConnectionExpired()) {
- await AuthUtil.instance.notifyReauthenticate()
- }
- return focusAmazonQPanel.execute(placeholder, 'amazonq.security.scan').then(() => {
- DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({
- sender: 'amazonqCore',
- command: 'review',
+ if (!codeReviewInChat) {
+ Commands.register('aws.amazonq.security.scan-statusbar', async () => {
+ if (AuthUtil.instance.isConnectionExpired()) {
+ await AuthUtil.instance.notifyReauthenticate()
+ }
+ return focusAmazonQPanel.execute(placeholder, 'amazonq.security.scan').then(() => {
+ DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({
+ sender: 'amazonqCore',
+ command: 'review',
+ })
})
})
- })
+ }
codeScanState.setChatControllers(scanChatControllerEventEmitters)
onDemandFileScanState.setChatControllers(scanChatControllerEventEmitters)
diff --git a/packages/amazonq/src/app/amazonqScan/models/constants.ts b/packages/amazonq/src/app/amazonqScan/models/constants.ts
index 93e815884e1..4180b130b78 100644
--- a/packages/amazonq/src/app/amazonqScan/models/constants.ts
+++ b/packages/amazonq/src/app/amazonqScan/models/constants.ts
@@ -97,3 +97,5 @@ const getIconForStep = (targetStep: number, currentStep: number) => {
? checkIcons.done
: checkIcons.wait
}
+
+export const codeReviewInChat = true
diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts
index 83e70b7bae3..fca3a132f90 100644
--- a/packages/amazonq/src/lsp/chat/commands.ts
+++ b/packages/amazonq/src/lsp/chat/commands.ts
@@ -6,10 +6,12 @@
import { Commands, globals } from 'aws-core-vscode/shared'
import { window } from 'vscode'
import { AmazonQChatViewProvider } from './webviewProvider'
-import { CodeScanIssue } from 'aws-core-vscode/codewhisperer'
+import { CodeScanIssue, AuthUtil } from 'aws-core-vscode/codewhisperer'
import { getLogger } from 'aws-core-vscode/shared'
import * as vscode from 'vscode'
import * as path from 'path'
+import { codeReviewInChat } from '../../app/amazonqScan/models/constants'
+import { telemetry, AmazonqCodeReviewTool } from 'aws-core-vscode/telemetry'
/**
* TODO: Re-enable these once we can figure out which path they're going to live in
@@ -29,7 +31,8 @@ export function registerCommands(provider: AmazonQChatViewProvider) {
filePath,
'Explain',
'Provide a small description of the issue. You must not attempt to fix the issue. You should only give a small summary of it to the user.',
- provider
+ provider,
+ 'explainIssue'
)
),
Commands.register('aws.amazonq.generateFix', (issue: CodeScanIssue, filePath: string) =>
@@ -38,7 +41,8 @@ export function registerCommands(provider: AmazonQChatViewProvider) {
filePath,
'Fix',
'Generate a fix for the following code issue. You must not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed.',
- provider
+ provider,
+ 'applyFix'
)
),
Commands.register('aws.amazonq.sendToPrompt', (data) => {
@@ -64,6 +68,11 @@ export function registerCommands(provider: AmazonQChatViewProvider) {
registerShellCommandShortCut('aws.amazonq.rejectCmdExecution', 'reject-shell-command', provider),
registerShellCommandShortCut('aws.amazonq.stopCmdExecution', 'stop-shell-command', provider)
)
+ if (codeReviewInChat) {
+ globals.context.subscriptions.push(
+ registerGenericCommand('aws.amazonq.security.scan-statusbar', 'Review', provider)
+ )
+ }
}
async function handleIssueCommand(
@@ -71,7 +80,8 @@ async function handleIssueCommand(
filePath: string,
action: string,
contextPrompt: string,
- provider: AmazonQChatViewProvider
+ provider: AmazonQChatViewProvider,
+ metricName: string
) {
await focusAmazonQPanel()
@@ -95,6 +105,16 @@ async function handleIssueCommand(
autoSubmit: true,
},
})
+
+ telemetry.amazonq_codeReviewTool.emit({
+ findingId: issue.findingId,
+ detectorId: issue.detectorId,
+ ruleId: issue.ruleId,
+ credentialStartUrl: AuthUtil.instance.startUrl,
+ autoDetected: issue.autoDetected,
+ result: 'Succeeded',
+ reason: metricName,
+ } as AmazonqCodeReviewTool)
}
async function openFileWithSelection(issue: CodeScanIssue, filePath: string) {
diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts
index d335dae40ef..7f265946af9 100644
--- a/packages/amazonq/src/lsp/client.ts
+++ b/packages/amazonq/src/lsp/client.ts
@@ -51,6 +51,7 @@ import { SessionManager } from '../app/inline/sessionManager'
import { LineTracker } from '../app/inline/stateTracker/lineTracker'
import { InlineTutorialAnnotation } from '../app/inline/tutorials/inlineTutorialAnnotation'
import { InlineChatTutorialAnnotation } from '../app/inline/tutorials/inlineChatTutorialAnnotation'
+import { codeReviewInChat } from '../app/amazonqScan/models/constants'
const localize = nls.loadMessageBundle()
const logger = getLogger('amazonqLsp.lspClient')
@@ -179,7 +180,7 @@ export async function startLanguageServer(
reroute: true,
modelSelection: true,
workspaceFilePath: vscode.workspace.workspaceFile?.fsPath,
- codeReviewInChat: false,
+ codeReviewInChat: codeReviewInChat,
},
window: {
notifications: true,