(node)
+ await openAwsConsoleCommand(sourceNode)
+ }),
+ Commands.register('aws.openAwsCFNConsole', async (node: StackNameNode) => {
+ await openAwsCFNConsoleCommand(node)
+ }),
Commands.register('aws.refreshAwsExplorerNode', async (element: AWSTreeNodeBase | undefined) => {
awsExplorer.refresh(element)
}),
diff --git a/packages/core/src/codecatalyst/devEnv.ts b/packages/core/src/codecatalyst/devEnv.ts
index cd45b353358..44db151d8de 100644
--- a/packages/core/src/codecatalyst/devEnv.ts
+++ b/packages/core/src/codecatalyst/devEnv.ts
@@ -98,7 +98,7 @@ export class DevEnvActivityStarter {
}
// If user is not authenticated, assume 15 minutes.
const inactivityTimeoutMin =
- devenvTimeoutMs > 0 ? devenvTimeoutMs / 60000 : thisDevenv?.summary.inactivityTimeoutMinutes ?? 15
+ devenvTimeoutMs > 0 ? devenvTimeoutMs / 60000 : (thisDevenv?.summary.inactivityTimeoutMinutes ?? 15)
if (!shouldSendActivity(inactivityTimeoutMin)) {
getLogger().info(
`codecatalyst: disabling DevEnvActivity heartbeat: configured to never timeout (inactivityTimeoutMinutes=${inactivityTimeoutMin})`
diff --git a/packages/core/src/codewhisperer/client/user-service-2.json b/packages/core/src/codewhisperer/client/user-service-2.json
index bda8d16922d..9defffe1063 100644
--- a/packages/core/src/codewhisperer/client/user-service-2.json
+++ b/packages/core/src/codewhisperer/client/user-service-2.json
@@ -39,6 +39,7 @@
"output": { "shape": "CreateTaskAssistConversationResponse" },
"errors": [
{ "shape": "ThrottlingException" },
+ { "shape": "ServiceQuotaExceededException" },
{ "shape": "InternalServerException" },
{ "shape": "ValidationException" },
{ "shape": "AccessDeniedException" }
@@ -56,6 +57,7 @@
"errors": [
{ "shape": "ThrottlingException" },
{ "shape": "ConflictException" },
+ { "shape": "ServiceQuotaExceededException" },
{ "shape": "ResourceNotFoundException" },
{ "shape": "InternalServerException" },
{ "shape": "ValidationException" },
@@ -278,6 +280,7 @@
"errors": [
{ "shape": "ThrottlingException" },
{ "shape": "ConflictException" },
+ { "shape": "ServiceQuotaExceededException" },
{ "shape": "ResourceNotFoundException" },
{ "shape": "InternalServerException" },
{ "shape": "ValidationException" },
@@ -336,6 +339,53 @@
"documentation": "Reason for AccessDeniedException
",
"enum": ["UNAUTHORIZED_CUSTOMIZATION_RESOURCE_ACCESS"]
},
+ "AppStudioState": {
+ "type": "structure",
+ "required": ["namespace", "propertyName", "propertyContext"],
+ "members": {
+ "namespace": {
+ "shape": "AppStudioStateNamespaceString",
+ "documentation": "The namespace of the context. Examples: 'ui.Button', 'ui.Table.DataSource', 'ui.Table.RowActions.Button', 'logic.invokeAWS', 'logic.JavaScript'
"
+ },
+ "propertyName": {
+ "shape": "AppStudioStatePropertyNameString",
+ "documentation": "The name of the property. Examples: 'visibility', 'disability', 'value', 'code'
"
+ },
+ "propertyValue": {
+ "shape": "AppStudioStatePropertyValueString",
+ "documentation": "The value of the property.
"
+ },
+ "propertyContext": {
+ "shape": "AppStudioStatePropertyContextString",
+ "documentation": "Context about how the property is used
"
+ }
+ },
+ "documentation": "Description of a user's context when they are calling Q Chat from AppStudio
"
+ },
+ "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
+ },
"ArtifactId": {
"type": "string",
"max": 126,
@@ -544,7 +594,8 @@
"members": {
"programmingLanguage": { "shape": "ProgrammingLanguage" },
"codeScanJobId": { "shape": "CodeScanJobId" },
- "timestamp": { "shape": "Timestamp" }
+ "timestamp": { "shape": "Timestamp" },
+ "codeAnalysisScope": { "shape": "CodeAnalysisScope" }
}
},
"CodeScanJobId": {
@@ -612,6 +663,18 @@
"documentation": "This exception is thrown when the action to perform could not be completed because the resource is in a conflicting state.
",
"exception": true
},
+ "ConsoleState": {
+ "type": "structure",
+ "members": {
+ "region": { "shape": "String" },
+ "consoleUrl": { "shape": "SensitiveString" },
+ "serviceId": { "shape": "String" },
+ "serviceConsolePage": { "shape": "String" },
+ "serviceSubconsolePage": { "shape": "String" },
+ "taskName": { "shape": "SensitiveString" }
+ },
+ "documentation": "Information about the state of the AWS management console page from which the user is calling
"
+ },
"ContentChecksumType": {
"type": "string",
"enum": ["SHA_256"]
@@ -700,9 +763,7 @@
"uploadId": { "shape": "UploadId" },
"uploadUrl": { "shape": "PreSignedUrl" },
"kmsKeyArn": { "shape": "ResourceArn" },
- "requestHeaders": {
- "shape": "RequestHeaders"
- }
+ "requestHeaders": { "shape": "RequestHeaders" }
}
},
"CursorState": {
@@ -860,6 +921,14 @@
"cursorState": {
"shape": "CursorState",
"documentation": "Position of the cursor
"
+ },
+ "relevantDocuments": {
+ "shape": "RelevantDocumentList",
+ "documentation": "Represents IDE provided relevant files
"
+ },
+ "useRelevantDocuments": {
+ "shape": "Boolean",
+ "documentation": "Whether service should use relevant document in prompt
"
}
},
"documentation": "Represents the state of an Editor
"
@@ -927,6 +996,13 @@
"max": 100,
"min": 0
},
+ "FeatureDevEvent": {
+ "type": "structure",
+ "required": ["conversationId"],
+ "members": {
+ "conversationId": { "shape": "ConversationId" }
+ }
+ },
"FeatureEvaluation": {
"type": "structure",
"required": ["feature", "variation", "value"],
@@ -1029,7 +1105,8 @@
"supplementalContexts": { "shape": "SupplementalContextList" },
"customizationArn": { "shape": "CustomizationArn" },
"optOutPreference": { "shape": "OptOutPreference" },
- "userContext": { "shape": "UserContext" }
+ "userContext": { "shape": "UserContext" },
+ "profileArn": { "shape": "ProfileArn" }
}
},
"GenerateCompletionsRequestMaxResultsInteger": {
@@ -1143,7 +1220,7 @@
},
"IdeCategory": {
"type": "string",
- "enum": ["JETBRAINS", "VSCODE", "CLI"],
+ "enum": ["JETBRAINS", "VSCODE", "CLI", "JUPYTER_MD", "JUPYTER_SM"],
"max": 64,
"min": 1
},
@@ -1170,6 +1247,29 @@
"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
@@ -1313,6 +1413,12 @@
"sensitive": true
},
"PrimitiveInteger": { "type": "integer" },
+ "ProfileArn": {
+ "type": "string",
+ "max": 950,
+ "min": 0,
+ "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}"
+ },
"ProgrammingLanguage": {
"type": "structure",
"required": ["languageName"],
@@ -1325,7 +1431,7 @@
"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)"
+ "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext)"
},
"ProgressUpdates": {
"type": "list",
@@ -1401,11 +1507,46 @@
"max": 10,
"min": 0
},
- "ResourceArn": {
+ "RelevantDocumentList": {
+ "type": "list",
+ "member": { "shape": "RelevantTextDocument" },
+ "max": 5,
+ "min": 0
+ },
+ "RelevantTextDocument": {
+ "type": "structure",
+ "required": ["relativeFilePath"],
+ "members": {
+ "relativeFilePath": {
+ "shape": "RelevantTextDocumentRelativeFilePathString",
+ "documentation": "Filepath relative to the root of the workspace
"
+ },
+ "programmingLanguage": {
+ "shape": "ProgrammingLanguage",
+ "documentation": "The text document's language identifier.
"
+ },
+ "text": {
+ "shape": "RelevantTextDocumentTextString",
+ "documentation": "Content of the text document
"
+ },
+ "documentSymbols": {
+ "shape": "DocumentSymbols",
+ "documentation": "DocumentSymbols parsed from a text document
"
+ }
+ },
+ "documentation": "Represents an IDE retrieved relevant Text Document / File
"
+ },
+ "RelevantTextDocumentRelativeFilePathString": {
"type": "string",
- "max": 1224,
+ "max": 4096,
+ "min": 1,
+ "sensitive": true
+ },
+ "RelevantTextDocumentTextString": {
+ "type": "string",
+ "max": 10240,
"min": 0,
- "pattern": "arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}"
+ "sensitive": true
},
"RequestHeaderKey": {
"type": "string",
@@ -1419,14 +1560,17 @@
},
"RequestHeaders": {
"type": "map",
- "key": {
- "shape": "RequestHeaderKey"
- },
- "value": {
- "shape": "RequestHeaderValue"
- },
+ "key": { "shape": "RequestHeaderKey" },
+ "value": { "shape": "RequestHeaderValue" },
"max": 16,
- "min": 1
+ "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",
@@ -1495,7 +1639,8 @@
},
"telemetryEvent": { "shape": "TelemetryEvent" },
"optOutPreference": { "shape": "OptOutPreference" },
- "userContext": { "shape": "UserContext" }
+ "userContext": { "shape": "UserContext" },
+ "profileArn": { "shape": "ProfileArn" }
}
},
"SendTelemetryEventResponse": {
@@ -1506,6 +1651,15 @@
"type": "string",
"sensitive": true
},
+ "ServiceQuotaExceededException": {
+ "type": "structure",
+ "required": ["message"],
+ "members": {
+ "message": { "shape": "String" }
+ },
+ "documentation": "This exception is thrown when request was denied due to caller exceeding their usage limits
",
+ "exception": true
+ },
"ShellHistory": {
"type": "list",
"member": { "shape": "ShellHistoryEntry" },
@@ -1609,11 +1763,11 @@
"members": {
"artifacts": { "shape": "ArtifactMap" },
"programmingLanguage": { "shape": "ProgrammingLanguage" },
- "scope": { "shape": "CodeAnalysisScope" },
"clientToken": {
"shape": "StartCodeAnalysisRequestClientTokenString",
"idempotencyToken": true
},
+ "scope": { "shape": "CodeAnalysisScope" },
"codeScanName": { "shape": "CodeScanName" }
}
},
@@ -1641,7 +1795,9 @@
"required": ["conversationState", "workspaceState"],
"members": {
"conversationState": { "shape": "ConversationState" },
- "workspaceState": { "shape": "WorkspaceState" }
+ "workspaceState": { "shape": "WorkspaceState" },
+ "taskAssistPlan": { "shape": "TaskAssistPlan" },
+ "currentCodeGenerationId": { "shape": "CodeGenerationId" }
},
"documentation": "Structure to represent start code generation request.
"
},
@@ -1770,6 +1926,63 @@
"type": "string",
"enum": ["DECLARATION", "USAGE"]
},
+ "TaskAssistPlan": {
+ "type": "list",
+ "member": { "shape": "TaskAssistPlanStep" },
+ "min": 0
+ },
+ "TaskAssistPlanStep": {
+ "type": "structure",
+ "required": ["filePath", "description"],
+ "members": {
+ "filePath": {
+ "shape": "TaskAssistPlanStepFilePathString",
+ "documentation": "File path on which the step is working on.
"
+ },
+ "description": {
+ "shape": "TaskAssistPlanStepDescriptionString",
+ "documentation": "Description on the step.
"
+ },
+ "startLine": {
+ "shape": "TaskAssistPlanStepStartLineInteger",
+ "documentation": "Start line number of the related changes.
"
+ },
+ "endLine": {
+ "shape": "TaskAssistPlanStepEndLineInteger",
+ "documentation": "End line number of the related changes.
"
+ },
+ "action": {
+ "shape": "TaskAssistPlanStepAction",
+ "documentation": "Type of the action.
"
+ }
+ },
+ "documentation": "Structured plan step for a task assist plan.
"
+ },
+ "TaskAssistPlanStepAction": {
+ "type": "string",
+ "documentation": "Action for task assist plan step
",
+ "enum": ["MODIFY", "CREATE", "DELETE", "UNKNOWN"]
+ },
+ "TaskAssistPlanStepDescriptionString": {
+ "type": "string",
+ "max": 1024,
+ "min": 1
+ },
+ "TaskAssistPlanStepEndLineInteger": {
+ "type": "integer",
+ "box": true,
+ "min": 0
+ },
+ "TaskAssistPlanStepFilePathString": {
+ "type": "string",
+ "max": 1024,
+ "min": 1
+ },
+ "TaskAssistPlanStepStartLineInteger": {
+ "type": "integer",
+ "box": true,
+ "min": 0
+ },
"TaskAssistPlanningUploadContext": {
"type": "structure",
"required": ["conversationId"],
@@ -1789,7 +2002,9 @@
"chatAddMessageEvent": { "shape": "ChatAddMessageEvent" },
"chatInteractWithMessageEvent": { "shape": "ChatInteractWithMessageEvent" },
"chatUserModificationEvent": { "shape": "ChatUserModificationEvent" },
- "terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" }
+ "terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" },
+ "featureDevEvent": { "shape": "FeatureDevEvent" },
+ "inlineChatEvent": { "shape": "InlineChatEvent" }
},
"union": true
},
@@ -1929,7 +2144,7 @@
},
"TransformationDownloadArtifactType": {
"type": "string",
- "enum": ["ClientInstructions", "Logs"]
+ "enum": ["ClientInstructions", "Logs", "GeneratedCode"]
},
"TransformationDownloadArtifacts": {
"type": "list",
@@ -1962,7 +2177,15 @@
},
"TransformationLanguage": {
"type": "string",
- "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP"]
+ "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP", "COBOL", "PL_I", "JCL"]
+ },
+ "TransformationLanguages": {
+ "type": "list",
+ "member": { "shape": "TransformationLanguage" }
+ },
+ "TransformationMainframeRuntimeEnv": {
+ "type": "string",
+ "enum": ["MAINFRAME"]
},
"TransformationOperatingSystemFamily": {
"type": "string",
@@ -1995,24 +2218,40 @@
},
"TransformationProgressUpdateStatus": {
"type": "string",
- "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED"]
+ "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED", "AWAITING_CLIENT_ACTION"]
+ },
+ "TransformationProjectArtifactDescriptor": {
+ "type": "structure",
+ "members": {
+ "sourceCodeArtifact": { "shape": "TransformationSourceCodeArtifactDescriptor" }
+ },
+ "union": true
},
"TransformationProjectState": {
"type": "structure",
"members": {
"language": { "shape": "TransformationLanguage" },
"runtimeEnv": { "shape": "TransformationRuntimeEnv" },
- "platformConfig": { "shape": "TransformationPlatformConfig" }
+ "platformConfig": { "shape": "TransformationPlatformConfig" },
+ "projectArtifact": { "shape": "TransformationProjectArtifactDescriptor" }
}
},
"TransformationRuntimeEnv": {
"type": "structure",
"members": {
"java": { "shape": "TransformationJavaRuntimeEnv" },
- "dotNet": { "shape": "TransformationDotNetRuntimeEnv" }
+ "dotNet": { "shape": "TransformationDotNetRuntimeEnv" },
+ "mainframe": { "shape": "TransformationMainframeRuntimeEnv" }
},
"union": true
},
+ "TransformationSourceCodeArtifactDescriptor": {
+ "type": "structure",
+ "members": {
+ "languages": { "shape": "TransformationLanguages" },
+ "runtimeEnv": { "shape": "TransformationRuntimeEnv" }
+ }
+ },
"TransformationSpec": {
"type": "structure",
"members": {
@@ -2066,11 +2305,11 @@
},
"TransformationType": {
"type": "string",
- "enum": ["LANGUAGE_UPGRADE"]
+ "enum": ["LANGUAGE_UPGRADE", "DOCUMENT_GENERATION"]
},
"TransformationUploadArtifactType": {
"type": "string",
- "enum": ["Dependencies"]
+ "enum": ["Dependencies", "ClientBuildResult"]
},
"TransformationUploadContext": {
"type": "structure",
@@ -2173,11 +2412,23 @@
},
"envState": {
"shape": "EnvState",
- "documentation": "Environment state chat messaage context.
"
+ "documentation": "Environment state chat message context.
"
+ },
+ "appStudioContext": {
+ "shape": "AppStudioState",
+ "documentation": "The state of a user's AppStudio UI when sending a message.
"
},
"diagnostic": {
"shape": "Diagnostic",
"documentation": "Diagnostic chat message context.
"
+ },
+ "consoleState": {
+ "shape": "ConsoleState",
+ "documentation": "Contextual information about the environment from which the user is calling.
"
+ },
+ "userSettings": {
+ "shape": "UserSettings",
+ "documentation": "Settings information, e.g., whether the user has enabled cross-region API calls.
"
}
},
"documentation": "Additional Chat message context associated with the Chat Message
"
@@ -2192,21 +2443,39 @@
"SHOW_EXAMPLES",
"CITE_SOURCES",
"EXPLAIN_LINE_BY_LINE",
- "EXPLAIN_CODE_SELECTION"
+ "EXPLAIN_CODE_SELECTION",
+ "GENERATE_CLOUDFORMATION_TEMPLATE"
]
},
"UserModificationEvent": {
"type": "structure",
- "required": ["sessionId", "requestId", "programmingLanguage", "modificationPercentage", "timestamp"],
+ "required": [
+ "sessionId",
+ "requestId",
+ "programmingLanguage",
+ "modificationPercentage",
+ "timestamp",
+ "acceptedCharacterCount",
+ "unmodifiedAcceptedCharacterCount"
+ ],
"members": {
"sessionId": { "shape": "UUID" },
"requestId": { "shape": "UUID" },
"programmingLanguage": { "shape": "ProgrammingLanguage" },
"modificationPercentage": { "shape": "Double" },
"customizationArn": { "shape": "CustomizationArn" },
- "timestamp": { "shape": "Timestamp" }
+ "timestamp": { "shape": "Timestamp" },
+ "acceptedCharacterCount": { "shape": "PrimitiveInteger" },
+ "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }
}
},
+ "UserSettings": {
+ "type": "structure",
+ "members": {
+ "hasConsentedToCrossRegionCalls": { "shape": "Boolean" }
+ },
+ "documentation": "Settings information passed by the Q widget
"
+ },
"UserTriggerDecisionEvent": {
"type": "structure",
"required": [
@@ -2230,7 +2499,9 @@
"triggerToResponseLatencyMilliseconds": { "shape": "Double" },
"suggestionReferenceCount": { "shape": "PrimitiveInteger" },
"generatedLine": { "shape": "PrimitiveInteger" },
- "numberOfRecommendations": { "shape": "PrimitiveInteger" }
+ "numberOfRecommendations": { "shape": "PrimitiveInteger" },
+ "perceivedLatencyMilliseconds": { "shape": "Double" },
+ "acceptedCharacterCount": { "shape": "PrimitiveInteger" }
}
},
"ValidationException": {
diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts
index 4f202eb93a8..53e693b8e69 100644
--- a/packages/core/src/codewhisperer/commands/basicCommands.ts
+++ b/packages/core/src/codewhisperer/commands/basicCommands.ts
@@ -41,8 +41,9 @@ import { ToolkitError, getTelemetryReason, getTelemetryReasonDesc } from '../../
import { isRemoteWorkspace } from '../../shared/vscode/env'
import { isBuilderIdConnection } from '../../auth/connection'
import globals from '../../shared/extensionGlobals'
-import { getVscodeCliPath, tryRun } from '../../shared/utilities/pathFind'
+import { getVscodeCliPath } from '../../shared/utilities/pathFind'
import { setContext } from '../../shared/vscode/setContext'
+import { tryRun } from '../../shared/utilities/pathFind'
const MessageTimeOut = 5_000
diff --git a/packages/core/src/codewhisperer/commands/startSecurityScan.ts b/packages/core/src/codewhisperer/commands/startSecurityScan.ts
index 8dee806f9df..58eaee2bf40 100644
--- a/packages/core/src/codewhisperer/commands/startSecurityScan.ts
+++ b/packages/core/src/codewhisperer/commands/startSecurityScan.ts
@@ -93,7 +93,8 @@ export async function startSecurityScan(
editor: vscode.TextEditor | undefined,
client: DefaultCodeWhispererClient,
context: vscode.ExtensionContext,
- scope: CodeWhispererConstants.CodeAnalysisScope
+ scope: CodeWhispererConstants.CodeAnalysisScope,
+ zipUtil: ZipUtil = new ZipUtil()
) {
const logger = getLoggerForScope(scope)
/**
@@ -130,7 +131,6 @@ export async function startSecurityScan(
* Step 1: Generate zip
*/
throwIfCancelled(scope, codeScanStartTime)
- const zipUtil = new ZipUtil()
const zipMetadata = await zipUtil.generateZip(editor?.document.uri, scope)
const projectPaths = zipUtil.getProjectPaths()
diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts
index 4d3c667f235..edf48ed00c5 100644
--- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts
+++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts
@@ -4,8 +4,9 @@
*/
import * as vscode from 'vscode'
-import * as fs from 'fs'
+import * as fs from 'fs' // eslint-disable-line no-restricted-imports
import * as os from 'os'
+import * as xml2js from 'xml2js'
import path from 'path'
import { getLogger } from '../../shared/logger'
import * as CodeWhispererConstants from '../models/constants'
@@ -15,9 +16,10 @@ import {
JDKVersion,
jobPlanProgress,
FolderInfo,
- TransformationCandidateProject,
ZipManifest,
TransformByQStatus,
+ DB,
+ TransformationType,
} from '../models/model'
import { convertDateToTimestamp } from '../../shared/utilities/textUtilities'
import {
@@ -37,7 +39,11 @@ import {
uploadPayload,
zipCode,
} from '../service/transformByQ/transformApiHandler'
-import { getOpenProjects, validateOpenProjects } from '../service/transformByQ/transformProjectValidationHandler'
+import {
+ getJavaProjects,
+ getOpenProjects,
+ validateOpenProjects,
+} from '../service/transformByQ/transformProjectValidationHandler'
import {
getVersionData,
prepareProjectDependencies,
@@ -81,17 +87,68 @@ function getFeedbackCommentData() {
return s
}
-export async function processTransformFormInput(
+export async function processLanguageUpgradeTransformFormInput(
pathToProject: string,
fromJDKVersion: JDKVersion,
toJDKVersion: JDKVersion
) {
+ transformByQState.setTransformationType(TransformationType.LANGUAGE_UPGRADE)
transformByQState.setProjectName(path.basename(pathToProject))
transformByQState.setProjectPath(pathToProject)
transformByQState.setSourceJDKVersion(fromJDKVersion)
transformByQState.setTargetJDKVersion(toJDKVersion)
}
+export async function processSQLConversionTransformFormInput(pathToProject: string, schema: string) {
+ transformByQState.setTransformationType(TransformationType.SQL_CONVERSION)
+ transformByQState.setProjectName(path.basename(pathToProject))
+ transformByQState.setProjectPath(pathToProject)
+ transformByQState.setSchema(schema)
+ transformByQState.setSourceJDKVersion(JDKVersion.JDK8) // use dummy value of JDK8 so that startJob API can be called
+ // targetJDKVersion defaults to JDK17, the only supported version, which is fine
+}
+
+export async function validateSQLMetadataFile(fileContents: string, message: any) {
+ try {
+ const sctData = await xml2js.parseStringPromise(fileContents)
+ const dbEntities = sctData['tree']['instances'][0]['ProjectModel'][0]['entities'][0]
+ const sourceDB = dbEntities['sources'][0]['DbServer'][0]['$']['vendor'].trim().toUpperCase()
+ const targetDB = dbEntities['targets'][0]['DbServer'][0]['$']['vendor'].trim().toUpperCase()
+ const sourceServerName = dbEntities['sources'][0]['DbServer'][0]['$']['name'].trim()
+ transformByQState.setSourceServerName(sourceServerName)
+ if (sourceDB !== DB.ORACLE) {
+ transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('unsupported-source-db', message.tabID)
+ return false
+ } else if (targetDB !== DB.AURORA_POSTGRESQL && targetDB !== DB.RDS_POSTGRESQL) {
+ transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('unsupported-target-db', message.tabID)
+ return false
+ }
+ transformByQState.setSourceDB(sourceDB)
+ transformByQState.setTargetDB(targetDB)
+
+ const serverNodeLocations =
+ sctData['tree']['instances'][0]['ProjectModel'][0]['relations'][0]['server-node-location']
+ const schemaNames = new Set()
+ serverNodeLocations.forEach((serverNodeLocation: any) => {
+ const schemaNodes = serverNodeLocation['FullNameNodeInfoList'][0]['nameParts'][0][
+ 'FullNameNodeInfo'
+ ].filter((node: any) => node['$']['typeNode'].toLowerCase() === 'schema')
+ schemaNodes.forEach((node: any) => {
+ schemaNames.add(node['$']['nameNode'].toUpperCase())
+ })
+ })
+ transformByQState.setSchemaOptions(schemaNames) // user will choose one of these
+ getLogger().info(
+ `CodeTransformation: Parsed .sct file with source DB: ${sourceDB}, target DB: ${targetDB}, source host name: ${sourceServerName}, and schema names: ${Array.from(schemaNames)}`
+ )
+ } catch (err: any) {
+ getLogger().error('CodeTransformation: Error parsing .sct file.', err)
+ transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('error-parsing-sct-file', message.tabID)
+ return false
+ }
+ return true
+}
+
export async function setMaven() {
let mavenWrapperExecutableName = os.platform() === 'win32' ? 'mvnw.cmd' : 'mvnw'
const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName)
@@ -278,8 +335,9 @@ export async function preTransformationUploadCode() {
// if the user chose to skip unit tests, add the custom build command here
transformZipManifest.customBuildCommand = transformByQState.getCustomBuildCommand()
const zipCodeResult = await zipCode({
- dependenciesFolder: transformByQState.getDependencyFolderInfo()!,
- modulePath: transformByQState.getProjectPath(),
+ // dependenciesFolder will be undefined for SQL conversions since we don't compileProject
+ dependenciesFolder: transformByQState.getDependencyFolderInfo(),
+ projectPath: transformByQState.getProjectPath(),
zipManifest: transformZipManifest,
})
@@ -607,6 +665,10 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string) {
throw new PollJobError()
}
}
+ if (transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION) {
+ // for now, no plan shown with SQL conversions. later, we may add one
+ return
+ }
let plan = undefined
try {
plan = await getTransformationPlan(jobId)
@@ -663,9 +725,16 @@ export async function finalizeTransformationJob(status: string) {
jobPlanProgress['transformCode'] = StepProgress.Succeeded
}
-export async function getValidCandidateProjects(): Promise {
+export async function getValidLanguageUpgradeCandidateProjects() {
+ const openProjects = await getOpenProjects()
+ const javaMavenProjects = await validateOpenProjects(openProjects)
+ return javaMavenProjects
+}
+
+export async function getValidSQLConversionCandidateProjects() {
const openProjects = await getOpenProjects()
- return validateOpenProjects(openProjects)
+ const javaProjects = await getJavaProjects(openProjects)
+ return javaProjects
}
export async function setTransformationToRunningState() {
@@ -717,20 +786,23 @@ export async function postTransformationJob() {
const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime())
const resultStatusMessage = transformByQState.getStatus()
- const versionInfo = await getVersionData()
- const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})`
- const javaVersionInfoMessage = `${versionInfo[1]} (${transformByQState.getMavenName()})`
-
- // Note: IntelliJ implementation of ResultStatusMessage includes additional metadata such as jobId.
- telemetry.codeTransform_totalRunTime.emit({
- buildSystemVersion: mavenVersionInfoMessage,
- codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
- codeTransformResultStatusMessage: resultStatusMessage,
- codeTransformRunTimeLatency: durationInMs,
- codeTransformLocalJavaVersion: javaVersionInfoMessage,
- result: resultStatusMessage === TransformByQStatus.Succeeded ? MetadataResult.Pass : MetadataResult.Fail,
- reason: resultStatusMessage,
- })
+ if (transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION) {
+ // the below is only applicable when user is doing a Java 8/11 language upgrade
+ const versionInfo = await getVersionData()
+ const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})`
+ const javaVersionInfoMessage = `${versionInfo[1]} (${transformByQState.getMavenName()})`
+
+ // Note: IntelliJ implementation of ResultStatusMessage includes additional metadata such as jobId.
+ telemetry.codeTransform_totalRunTime.emit({
+ buildSystemVersion: mavenVersionInfoMessage,
+ codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
+ codeTransformResultStatusMessage: resultStatusMessage,
+ codeTransformRunTimeLatency: durationInMs,
+ codeTransformLocalJavaVersion: javaVersionInfoMessage,
+ result: resultStatusMessage === TransformByQStatus.Succeeded ? MetadataResult.Pass : MetadataResult.Fail,
+ reason: resultStatusMessage,
+ })
+ }
if (transformByQState.isSucceeded()) {
void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification)
diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts
index af056f8c76d..1c6423f7c86 100644
--- a/packages/core/src/codewhisperer/index.ts
+++ b/packages/core/src/codewhisperer/index.ts
@@ -25,6 +25,7 @@ export type {
Completion,
SendTelemetryEventResponse,
TelemetryEvent,
+ InlineChatEvent,
} from './client/codewhispereruserclient.d.ts'
export type { default as CodeWhispererUserClient } from './client/codewhispereruserclient.d.ts'
export { SecurityPanelViewProvider } from './views/securityPanelViewProvider'
@@ -87,3 +88,5 @@ export * as supplementalContextUtil from './util/supplementalContext/supplementa
export * from './service/diagnosticsProvider'
export * as diagnosticsProvider from './service/diagnosticsProvider'
export * from './ui/codeWhispererNodes'
+export { getSelectedCustomization } from './util/customizationUtil'
+export { Container } from './service/serviceContainer'
diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts
index 41347803bd1..52b5b27d3e8 100644
--- a/packages/core/src/codewhisperer/models/constants.ts
+++ b/packages/core/src/codewhisperer/models/constants.ts
@@ -22,6 +22,20 @@ export const AWSTemplateKeyWords = ['AWSTemplateFormatVersion', 'Resources', 'AW
export const AWSTemplateCaseInsensitiveKeyWords = ['cloudformation', 'cfn', 'template', 'description']
+export const JsonConfigFileNamingConvention = new Set([
+ 'app.json',
+ 'appsettings.json',
+ 'bower.json',
+ 'composer.json',
+ 'db.json',
+ 'manifest.json',
+ 'package.json',
+ 'schema.json',
+ 'settings.json',
+ 'tsconfig.json',
+ 'vcpkg.json',
+])
+
export const normalTextChangeRegex = /[A-Za-z0-9]/g
export const autoSuggestionConfig = {
@@ -98,6 +112,14 @@ export const platformLanguageIds = [
'packer',
'plaintext',
'jsonc',
+ 'systemverilog',
+ 'verilog',
+ 'powershell',
+ 'dart',
+ 'lua',
+ 'r',
+ 'swift',
+ 'vue',
] as const
export type PlatformLanguageId = (typeof platformLanguageIds)[number]
@@ -328,6 +350,7 @@ export const updateInlineLockKey = 'CODEWHISPERER_INLINE_UPDATE_LOCK_KEY'
export const newCustomizationMessage = 'You have access to new Amazon Q customizations.'
// Start of QCT Strings
+
export const uploadZipSizeLimitInBytes = 2000000000 // 2GB
export const maxBufferSize = 1024 * 1024 * 8 // this is 8MB; the default max buffer size for stdout for spawnSync is 1MB
@@ -420,20 +443,20 @@ export const codeTransformLocThreshold = 100000
export const jobStartedChatMessage =
'I am starting to transform your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your project. To monitor progress, go to the Transformation Hub. If I run into any issues, I might pause the transformation to get input from you on how to proceed.'
-export const uploadingCodeStepMessage = 'Uploading your code'
+export const uploadingCodeStepMessage = 'Upload your code'
export const buildCodeStepMessage = 'Build uploaded code in secure build environment'
export const generatePlanStepMessage = 'Generate transformation plan'
-export const transformStepMessage = 'Transform your code to Java 17 using transformation plan'
+export const transformStepMessage = 'Transform your code'
export const filesUploadedMessage =
'Files have been uploaded to Amazon Q, transformation job has been accepted and is preparing to start.'
export const planningMessage = 'Amazon Q is analyzing your code in order to generate a transformation plan.'
-export const transformingMessage = 'Amazon Q is transforming your code. Details will appear soon.'
+export const transformingMessage = 'Amazon Q is transforming your code.'
export const stoppingJobMessage = 'Stopping the transformation...'
@@ -476,6 +499,24 @@ export const absolutePathDetectedMessage = (numPaths: number, buildFile: string,
export const unsupportedJavaVersionChatMessage = `Sorry, currently I can only upgrade Java 8 or Java 11 projects. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).`
+export const selectSQLMetadataFileHelpMessage =
+ 'Next, I need the zipped metadata file from your schema conversion. You can download the metadata by going to your migration project in the AWS DMS console. Open the schema conversion and choose **Convert the embedded SQL in your application**. You can downloaded the metadata from Amazon S3 in the {schema-conversion-project}/ directory.'
+
+export const invalidMetadataFileUnsupportedSourceDB =
+ 'I can only convert SQL for migrations from an Oracle source database. The provided .sct file indicates another source database for this migration.'
+
+export const invalidMetadataFileUnsupportedTargetDB =
+ 'I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases. The provided .sct file indicates another target database for this migration.'
+
+export const invalidMetadataFileErrorParsing =
+ "It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS."
+
+export const invalidMetadataFileNoSctFile =
+ "An .sct file is required for transformation. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS."
+
+export const sqlMetadataFileReceived =
+ 'I found the following source database, target database, and host based on the schema conversion metadata you provided:'
+
export const failedToStartJobChatMessage =
"Sorry, I couldn't begin the transformation. Please try starting the transformation again."
@@ -531,16 +572,16 @@ export const jobCancelledChatMessage =
export const jobCancelledNotification = 'You cancelled the transformation.'
export const jobCompletedChatMessage =
- 'I upgraded your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.'
+ 'I upgraded your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.'
export const jobCompletedNotification =
- 'Amazon Q upgraded your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.'
+ 'Amazon Q upgraded your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.'
export const jobPartiallyCompletedChatMessage =
- 'I upgraded part of your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.'
+ 'I upgraded part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.'
export const jobPartiallyCompletedNotification =
- 'Amazon Q upgraded part of your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.'
+ 'Amazon Q upgraded part of your code. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.'
export const noPomXmlFoundChatMessage = `Sorry, I couldn\'t find a project that I can upgrade. I couldn\'t find a pom.xml file in any of your open projects. Currently, I can only upgrade Java 8 or Java 11 projects built on Maven. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).`
@@ -604,7 +645,7 @@ export const cleanInstallErrorNotification = `Amazon Q could not run the Maven c
export const enterJavaHomeChatMessage = 'Enter the path to JDK '
export const projectPromptChatMessage =
- 'I can upgrade your JAVA_VERSION_HERE. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Transform.'
+ 'I can upgrade your JAVA_VERSION_HERE. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Confirm.'
export const windowsJavaHomeHelpChatMessage =
'To find the JDK path, run the following commands in a new terminal: `cd "C:/Program Files/Java"` and then `dir`. If you see your JDK version, run `cd ` and then `cd` to show the path.'
@@ -635,7 +676,7 @@ export const chooseTargetVersionFormTitle = 'Choose the target code version'
export const skipUnitTestsFormTitle = 'Choose to skip unit tests'
export const skipUnitTestsFormMessage =
- 'I will build your project using `mvn test` by default. If you would like me to build your project without running unit tests, I will use `mvn test-compile`.'
+ 'I will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`.'
export const runUnitTestsMessage = 'Run unit tests'
@@ -689,7 +730,8 @@ export const runSecurityScanButtonTitle = 'Run security scan'
export const crossFileContextConfig = {
numberOfChunkToFetch: 60,
topK: 3,
- numberOfLinesEachChunk: 10,
+ numberOfLinesEachChunk: 50,
+ maximumTotalLength: 20480,
}
export const utgConfig = {
diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts
index 7e649d75e78..257ffedb403 100644
--- a/packages/core/src/codewhisperer/models/model.ts
+++ b/packages/core/src/codewhisperer/models/model.ts
@@ -17,6 +17,7 @@ import { References } from '../client/codewhisperer'
import globals from '../../shared/extensionGlobals'
import { ChatControllerEventEmitters } from '../../amazonqGumby/chat/controller/controller'
import { TransformationSteps } from '../client/codewhispereruserclient'
+import { Messenger } from '../../amazonqGumby/chat/controller/messenger/messenger'
// unavoidable global variables
interface VsCodeState {
@@ -48,7 +49,7 @@ export type UtgStrategy = 'ByName' | 'ByContent'
export type CrossFileStrategy = 'OpenTabs_BM25'
-export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty'
+export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty' | 'LSP'
export interface CodeWhispererSupplementalContext {
isUtg: boolean
@@ -283,6 +284,11 @@ export enum TransformByQStatus {
PartiallySucceeded = 'Partially Succeeded',
}
+export enum TransformationType {
+ LANGUAGE_UPGRADE = 'Language Upgrade',
+ SQL_CONVERSION = 'SQL Conversion',
+}
+
export enum TransformByQReviewStatus {
NotStarted = 'NotStarted',
PreparingReview = 'PreparingReview',
@@ -303,6 +309,13 @@ export enum JDKVersion {
UNSUPPORTED = 'UNSUPPORTED',
}
+export enum DB {
+ ORACLE = 'ORACLE',
+ RDS_POSTGRESQL = 'RDS_POSTGRESQL',
+ AURORA_POSTGRESQL = 'AURORA_POSTGRESQL',
+ OTHER = 'OTHER',
+}
+
export enum BuildSystem {
Maven = 'Maven',
Gradle = 'Gradle',
@@ -311,12 +324,21 @@ export enum BuildSystem {
export class ZipManifest {
sourcesRoot: string = 'sources/'
- dependenciesRoot: string | undefined = 'dependencies/'
+ dependenciesRoot: string = 'dependencies/'
buildLogs: string = 'build-logs.txt'
version: string = '1.0'
hilCapabilities: string[] = ['HIL_1pDependency_VersionUpgrade']
- transformCapabilities: string[] = ['EXPLAINABILITY_V1']
+ transformCapabilities: string[] = ['EXPLAINABILITY_V1'] // TO-DO: for SQL conversions, maybe make this = []
customBuildCommand: string = 'clean test'
+ requestedConversions?: {
+ sqlConversion?: {
+ source?: string
+ target?: string
+ schema?: string
+ host?: string
+ sctFileName?: string
+ }
+ }
}
export interface IHilZipManifestParams {
@@ -364,6 +386,8 @@ export let sessionJobHistory: {
export class TransformByQState {
private transformByQState: TransformByQStatus = TransformByQStatus.NotStarted
+ private transformationType: TransformationType | undefined = undefined
+
private projectName: string = ''
private projectPath: string = ''
@@ -377,6 +401,18 @@ export class TransformByQState {
private customBuildCommand: string = ''
+ private sourceDB: DB | undefined = undefined
+
+ private targetDB: DB | undefined = undefined
+
+ private schema: string = ''
+
+ private schemaOptions: Set = new Set()
+
+ private sourceServerName: string = ''
+
+ private metadataPathSQL: string = ''
+
private planFilePath: string = ''
private summaryFilePath: string = ''
private preBuildLogFilePath: string = ''
@@ -401,6 +437,7 @@ export class TransformByQState {
private javaHome: string | undefined = undefined
private chatControllers: ChatControllerEventEmitters | undefined = undefined
+ private chatMessenger: Messenger | undefined = undefined
private dependencyFolderInfo: FolderInfo | undefined = undefined
@@ -432,6 +469,10 @@ export class TransformByQState {
return this.transformByQState === TransformByQStatus.PartiallySucceeded
}
+ public getTransformationType() {
+ return this.transformationType
+ }
+
public getProjectName() {
return this.projectName
}
@@ -464,6 +505,30 @@ export class TransformByQState {
return this.targetJDKVersion
}
+ public getSourceDB() {
+ return this.sourceDB
+ }
+
+ public getTargetDB() {
+ return this.targetDB
+ }
+
+ public getSchema() {
+ return this.schema
+ }
+
+ public getSchemaOptions() {
+ return this.schemaOptions
+ }
+
+ public getSourceServerName() {
+ return this.sourceServerName
+ }
+
+ public getMetadataPathSQL() {
+ return this.metadataPathSQL
+ }
+
public getStatus() {
return this.transformByQState
}
@@ -520,6 +585,10 @@ export class TransformByQState {
return this.chatControllers
}
+ public getChatMessenger() {
+ return this.chatMessenger
+ }
+
public getDependencyFolderInfo(): FolderInfo | undefined {
return this.dependencyFolderInfo
}
@@ -560,6 +629,10 @@ export class TransformByQState {
this.transformByQState = TransformByQStatus.PartiallySucceeded
}
+ public setTransformationType(type: TransformationType) {
+ this.transformationType = type
+ }
+
public setProjectName(name: string) {
this.projectName = name
}
@@ -588,6 +661,30 @@ export class TransformByQState {
this.targetJDKVersion = version
}
+ public setSourceDB(db: DB) {
+ this.sourceDB = db
+ }
+
+ public setTargetDB(db: DB) {
+ this.targetDB = db
+ }
+
+ public setSchema(schema: string) {
+ this.schema = schema
+ }
+
+ public setSchemaOptions(schemaOptions: Set) {
+ this.schemaOptions = schemaOptions
+ }
+
+ public setSourceServerName(serverName: string) {
+ this.sourceServerName = serverName
+ }
+
+ public setMetadataPathSQL(path: string) {
+ this.metadataPathSQL = path
+ }
+
public setPlanFilePath(filePath: string) {
this.planFilePath = filePath
}
@@ -636,6 +733,10 @@ export class TransformByQState {
this.chatControllers = controllers
}
+ public setChatMessenger(messenger: Messenger) {
+ this.chatMessenger = messenger
+ }
+
public setDependencyFolderInfo(folderInfo: FolderInfo) {
this.dependencyFolderInfo = folderInfo
}
@@ -666,6 +767,14 @@ export class TransformByQState {
this.jobFailureErrorChatMessage = undefined
this.jobFailureMetadata = ''
this.payloadFilePath = ''
+ this.metadataPathSQL = ''
+ this.sourceJDKVersion = undefined
+ this.targetJDKVersion = JDKVersion.JDK17
+ this.sourceDB = undefined
+ this.targetDB = undefined
+ this.sourceServerName = ''
+ this.schemaOptions.clear()
+ this.schema = ''
this.errorLog = ''
this.customBuildCommand = ''
this.intervalId = undefined
diff --git a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts
index b2571ece38c..e5ac2212e06 100644
--- a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts
+++ b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts
@@ -169,6 +169,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt
ImportAdderProvider.instance.onShowRecommendation(document, this.startPos.line, r)
this.nextMove = 0
TelemetryHelper.instance.setFirstSuggestionShowTime()
+ session.setPerceivedLatency()
this._onDidShow.fire()
if (matchedCount >= 2 || this.nextToken !== '') {
const result = [item]
diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts
index 69bd9bdb887..715fd93ad2d 100644
--- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts
+++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts
@@ -112,7 +112,6 @@ export class InlineCompletionService {
await this.setState('loading')
- TelemetryHelper.instance.setInvocationStartTime(performance.now())
RecommendationHandler.instance.checkAndResetCancellationTokens()
RecommendationHandler.instance.documentUri = editor.document.uri
let response: GetRecommendationsResponse = {
diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts
index fcd7fb2b84a..5792a92142a 100644
--- a/packages/core/src/codewhisperer/service/recommendationHandler.ts
+++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts
@@ -14,7 +14,7 @@ import { AWSError } from 'aws-sdk'
import { isAwsError } from '../../shared/errors'
import { TelemetryHelper } from '../util/telemetryHelper'
import { getLogger } from '../../shared/logger'
-import { isCloud9, isSageMaker } from '../../shared/extensionUtilities'
+import { isCloud9 } from '../../shared/extensionUtilities'
import { hasVendedIamCredentials } from '../../auth/auth'
import {
asyncCallWithTimeout,
@@ -38,12 +38,12 @@ import globals from '../../shared/extensionGlobals'
import { noSuggestions, updateInlineLockKey } from '../models/constants'
import AsyncLock from 'async-lock'
import { AuthUtil } from '../util/authUtil'
-import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil'
import { CWInlineCompletionItemProvider } from './inlineCompletionItemProvider'
import { application } from '../util/codeWhispererApplication'
import { openUrl } from '../../shared/utilities/vsCodeUtils'
import { indent } from '../../shared/utilities/textUtilities'
import path from 'path'
+import { isIamConnection } from '../../auth/connection'
/**
* This class is for getRecommendation/listRecommendation API calls and its states
@@ -159,8 +159,7 @@ export class RecommendationHandler {
autoTriggerType?: CodewhispererAutomatedTriggerType,
pagination: boolean = true,
page: number = 0,
- isSM: boolean = isSageMaker(),
- retry: boolean = false
+ generate: boolean = isIamConnection(AuthUtil.instance.conn)
): Promise {
let invocationResult: 'Succeeded' | 'Failed' = 'Failed'
let errorMessage: string | undefined = undefined
@@ -187,7 +186,7 @@ export class RecommendationHandler {
).language
session.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName)
- if (pagination && !isSM) {
+ if (pagination && !generate) {
if (page === 0) {
session.requestContext = await EditorContext.buildListRecommendationRequest(
editor as vscode.TextEditor,
@@ -242,7 +241,9 @@ export class RecommendationHandler {
this.lastInvocationTime = startTime
const mappedReq = runtimeLanguageContext.mapToRuntimeLanguage(request)
const codewhispererPromise =
- pagination && !isSM ? client.listRecommendations(mappedReq) : client.generateRecommendations(mappedReq)
+ pagination && !generate
+ ? client.listRecommendations(mappedReq)
+ : client.generateRecommendations(mappedReq)
const resp = await this.getServerResponse(triggerType, config.isManualTriggerEnabled, codewhispererPromise)
TelemetryHelper.instance.setSdkApiCallEndTime()
latency = startTime !== 0 ? performance.now() - startTime : 0
@@ -257,7 +258,7 @@ export class RecommendationHandler {
sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid']
TelemetryHelper.instance.setFirstResponseRequestId(requestId)
if (page === 0) {
- TelemetryHelper.instance.setTimeToFirstRecommendation(performance.now())
+ session.setTimeToFirstRecommendation(performance.now())
}
if (nextToken === '') {
TelemetryHelper.instance.setAllPaginationEndTime()
@@ -702,7 +703,6 @@ export class RecommendationHandler {
duration: performance.now() - this.lastInvocationTime,
passive: true,
credentialStartUrl: AuthUtil.instance.startUrl,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
result: 'Succeeded',
})
}
diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts
index e746ad6e108..86480f0766e 100644
--- a/packages/core/src/codewhisperer/service/securityScanHandler.ts
+++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts
@@ -16,7 +16,7 @@ import {
import { sleep } from '../../shared/utilities/timeoutUtils'
import * as codewhispererClient from '../client/codewhisperer'
import * as CodeWhispererConstants from '../models/constants'
-import { existsSync, statSync, readFileSync } from 'fs'
+import { existsSync, statSync, readFileSync } from 'fs' // eslint-disable-line no-restricted-imports
import { RawCodeScanIssue } from '../models/model'
import * as crypto from 'crypto'
import path = require('path')
@@ -310,7 +310,7 @@ export async function uploadArtifactToS3(
)
const errorMessage = getTelemetryReasonDesc(error)?.includes(`"PUT" request failed with code "403"`)
? `"PUT" request failed with code "403"`
- : getTelemetryReasonDesc(error) ?? 'Security scan failed.'
+ : (getTelemetryReasonDesc(error) ?? 'Security scan failed.')
throw new UploadArtifactToS3Error(errorMessage)
}
diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts
index b10418bd3af..55cd7797e5e 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
-import * as nodefs from 'fs'
+import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports
import * as path from 'path'
import * as os from 'os'
import * as codeWhisperer from '../../client/codewhisperer'
@@ -16,6 +16,7 @@ import {
jobPlanProgress,
sessionJobHistory,
StepProgress,
+ TransformationType,
transformByQState,
TransformByQStatus,
TransformByQStoppedError,
@@ -212,6 +213,11 @@ export async function uploadPayload(payloadFileName: string, uploadContext?: Upl
transformByQState.setJobId(encodeHTML(response.uploadId))
}
jobPlanProgress['uploadCode'] = StepProgress.Succeeded
+ if (transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION) {
+ // if doing a SQL conversion, we don't build the code or generate a plan, so mark these steps as succeeded immediately so that next step renders
+ jobPlanProgress['buildCode'] = StepProgress.Succeeded
+ jobPlanProgress['generatePlan'] = StepProgress.Succeeded
+ }
updateJobHistory()
return response.uploadId
}
@@ -275,9 +281,9 @@ export function createZipManifest({ hilZipParams }: IZipManifestParams) {
}
interface IZipCodeParams {
- dependenciesFolder: FolderInfo
+ dependenciesFolder?: FolderInfo
humanInTheLoopFlag?: boolean
- modulePath?: string
+ projectPath?: string
zipManifest: ZipManifest | HilZipManifest
}
@@ -287,25 +293,27 @@ interface ZipCodeResult {
fileSize: number
}
-export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePath, zipManifest }: IZipCodeParams) {
+export async function zipCode(
+ { dependenciesFolder, humanInTheLoopFlag, projectPath, zipManifest }: IZipCodeParams,
+ zip: AdmZip = new AdmZip()
+) {
let tempFilePath = undefined
let logFilePath = undefined
let dependenciesCopied = false
try {
throwIfCancelled()
- const zip = new AdmZip()
- // If no modulePath is passed in, we are not uploaded the source folder
- // NOTE: We only upload dependencies for human in the loop work
- if (modulePath) {
- const sourceFiles = getFilesRecursively(modulePath, false)
+ // if no project Path is passed in, we are not uploaded the source folder
+ // we only upload dependencies for human in the loop work
+ if (projectPath) {
+ const sourceFiles = getFilesRecursively(projectPath, false)
let sourceFilesSize = 0
for (const file of sourceFiles) {
if (nodefs.statSync(file).isDirectory()) {
getLogger().info('CodeTransformation: Skipping directory, likely a symlink')
continue
}
- const relativePath = path.relative(modulePath, file)
+ const relativePath = path.relative(projectPath, file)
const paddedPath = path.join('sources', relativePath)
zip.addLocalFile(file, path.dirname(paddedPath))
sourceFilesSize += (await nodefs.promises.stat(file)).size
@@ -313,14 +321,40 @@ export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePa
getLogger().info(`CodeTransformation: source code files size = ${sourceFilesSize}`)
}
+ if (
+ transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION &&
+ zipManifest instanceof ZipManifest
+ ) {
+ // note that zipManifest must be a ZipManifest since only other option is HilZipManifest which is not used for SQL conversions
+ const metadataZip = new AdmZip(transformByQState.getMetadataPathSQL())
+ zipManifest.requestedConversions = {
+ sqlConversion: {
+ source: transformByQState.getSourceDB(),
+ target: transformByQState.getTargetDB(),
+ schema: transformByQState.getSchema(),
+ host: transformByQState.getSourceServerName(),
+ sctFileName: metadataZip.getEntries().filter((entry) => entry.entryName.endsWith('.sct'))[0]
+ .entryName,
+ },
+ }
+ // TO-DO: later consider making this add to path.join(zipManifest.dependenciesRoot, 'qct-sct-metadata', entry.entryName) so that it's more organized
+ metadataZip
+ .getEntries()
+ .forEach((entry) =>
+ zip.addFile(path.join(zipManifest.dependenciesRoot, entry.entryName), entry.getData())
+ )
+ const sqlMetadataSize = (await nodefs.promises.stat(transformByQState.getMetadataPathSQL())).size
+ getLogger().info(`CodeTransformation: SQL metadata file size = ${sqlMetadataSize}`)
+ }
+
throwIfCancelled()
let dependencyFiles: string[] = []
- if (await fs.exists(dependenciesFolder.path)) {
+ if (dependenciesFolder && (await fs.exists(dependenciesFolder.path))) {
dependencyFiles = getFilesRecursively(dependenciesFolder.path, true)
}
- if (dependencyFiles.length > 0) {
+ if (dependenciesFolder && dependencyFiles.length > 0) {
let dependencyFilesSize = 0
for (const file of dependencyFiles) {
if (isExcludedDependencyFile(file)) {
@@ -334,10 +368,6 @@ export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePa
}
getLogger().info(`CodeTransformation: dependency files size = ${dependencyFilesSize}`)
dependenciesCopied = true
- } else {
- if (zipManifest instanceof ZipManifest) {
- zipManifest.dependenciesRoot = undefined
- }
}
zip.addFile('manifest.json', Buffer.from(JSON.stringify(zipManifest)), 'utf-8')
@@ -354,10 +384,11 @@ export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePa
tempFilePath = path.join(os.tmpdir(), 'zipped-code.zip')
await fs.writeFile(tempFilePath, zip.toBuffer())
- if (await fs.exists(dependenciesFolder.path)) {
+ if (dependenciesFolder && (await fs.exists(dependenciesFolder.path))) {
await fs.delete(dependenciesFolder.path, { recursive: true, force: true })
}
} catch (e: any) {
+ getLogger().error(`CodeTransformation: zipCode error = ${e}`)
throw Error('Failed to zip project')
} finally {
if (logFilePath) {
@@ -392,9 +423,9 @@ export async function startJob(uploadId: string) {
programmingLanguage: { languageName: CodeWhispererConstants.defaultLanguage.toLowerCase() },
},
transformationSpec: {
- transformationType: CodeWhispererConstants.transformationType,
- source: { language: sourceLanguageVersion },
- target: { language: targetLanguageVersion },
+ transformationType: CodeWhispererConstants.transformationType, // shared b/w language upgrades & sql conversions for now
+ source: { language: sourceLanguageVersion }, // dummy value of JDK8 used for SQL conversions just so that this API can be called
+ target: { language: targetLanguageVersion }, // always JDK17
},
})
if (response.$response.requestId) {
diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
index d7040a11cd6..f6c5e24bed1 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts
@@ -8,7 +8,7 @@ import * as path from 'path'
import * as os from 'os'
import xml2js = require('xml2js')
import * as CodeWhispererConstants from '../../models/constants'
-import { existsSync, writeFileSync } from 'fs'
+import { existsSync, writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports
import { BuildSystem, FolderInfo, transformByQState } from '../../models/model'
import { IManifestFile } from '../../../amazonqFeatureDev/models'
import fs from '../../../shared/fs/fs'
diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts
index c0f47e88d4c..143f20af51a 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts
@@ -17,7 +17,7 @@ import { checkBuildSystem } from './transformFileHandler'
export async function getOpenProjects(): Promise {
const folders = vscode.workspace.workspaceFolders
- if (folders === undefined) {
+ if (folders === undefined || folders.length === 0) {
throw new NoOpenProjectsError()
}
@@ -32,7 +32,7 @@ export async function getOpenProjects(): Promise= 1
+ activeStepId >= 1 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show buildCode step
? simpleStep(
this.getProgressIconMarkup(jobPlanProgress['buildCode']),
CodeWhispererConstants.buildCodeStepMessage,
@@ -339,7 +356,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider
)
: ''
const planMarkup =
- activeStepId >= 2
+ activeStepId >= 2 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show generatePlan step
? simpleStep(
this.getProgressIconMarkup(jobPlanProgress['generatePlan']),
CodeWhispererConstants.generatePlanStepMessage,
diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts
index 2f31027fecc..2f75795c251 100644
--- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts
+++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts
@@ -5,7 +5,7 @@
import AdmZip from 'adm-zip'
import os from 'os'
-import fs from 'fs'
+import fs from 'fs' // eslint-disable-line no-restricted-imports
import { parsePatch, applyPatches, ParsedDiff } from 'diff'
import path from 'path'
import vscode from 'vscode'
diff --git a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts
index b4c842b0413..925609ce185 100644
--- a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts
+++ b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts
@@ -8,15 +8,14 @@ import { getLogger } from '../../shared/logger/logger'
import * as CodeWhispererConstants from '../models/constants'
import globals from '../../shared/extensionGlobals'
import { vsCodeState } from '../models/model'
-import { distance } from 'fastest-levenshtein'
import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry'
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
import { TelemetryHelper } from '../util/telemetryHelper'
import { AuthUtil } from '../util/authUtil'
-import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil'
import { getSelectedCustomization } from '../util/customizationUtil'
import { codeWhispererClient as client } from '../client/codewhisperer'
import { isAwsError } from '../../shared/errors'
+import { getUnmodifiedAcceptedTokens } from '../util/commonUtil'
interface CodeWhispererToken {
range: vscode.Range
@@ -87,18 +86,10 @@ export class CodeWhispererCodeCoverageTracker {
for (let i = 0; i < this._acceptedTokens[filename].length; i++) {
const oldText = this._acceptedTokens[filename][i].text
const newText = editor.document.getText(this._acceptedTokens[filename][i].range)
- this._acceptedTokens[filename][i].accepted = this.getUnmodifiedAcceptedTokens(oldText, newText)
+ this._acceptedTokens[filename][i].accepted = getUnmodifiedAcceptedTokens(oldText, newText)
}
}
}
- // With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
- // and thus the unmodified part of recommendation length can be deducted/approximated
- // ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
- // ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
- // ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
- public getUnmodifiedAcceptedTokens(origin: string, after: string) {
- return Math.max(origin.length, after.length) - distance(origin, after)
- }
public emitCodeWhispererCodeContribution() {
let totalTokens = 0
@@ -134,7 +125,6 @@ export class CodeWhispererCodeCoverageTracker {
codewhispererSuggestedTokens: acceptedTokens,
codewhispererPercentage: percentage ? percentage : 0,
successCount: this._serviceInvocationCount,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn,
credentialStartUrl: AuthUtil.instance.startUrl,
})
diff --git a/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts b/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts
index e15c43e42ee..05a6d83f3f0 100644
--- a/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts
+++ b/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts
@@ -9,14 +9,14 @@ import { distance } from 'fastest-levenshtein'
import { AcceptedSuggestionEntry } from '../models/model'
import { getLogger } from '../../shared/logger/logger'
import { AmazonqModifyCode, telemetry } from '../../shared/telemetry/telemetry'
-import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil'
import { AuthUtil } from '../util/authUtil'
import { InsertedCode } from '../../codewhispererChat/controllers/chat/model'
import { codeWhispererClient } from '../client/codewhisperer'
import { logSendTelemetryEventFailure } from '../../codewhispererChat/controllers/chat/telemetryHelper'
import { Timeout } from '../../shared/utilities/timeoutUtils'
import { getSelectedCustomization } from '../util/customizationUtil'
-import { undefinedIfEmpty } from '../../shared'
+import { isAwsError, undefinedIfEmpty } from '../../shared'
+import { getUnmodifiedAcceptedTokens } from '../util/commonUtil'
/**
* This singleton class is mainly used for calculating the percentage of user modification.
@@ -90,19 +90,20 @@ export class CodeWhispererTracker {
public async emitTelemetryOnSuggestion(suggestion: AcceptedSuggestionEntry | InsertedCode) {
let percentage = 1.0
+ let currString = ''
+ const customizationArn = undefinedIfEmpty(getSelectedCustomization().arn)
try {
if (suggestion.fileUrl?.scheme !== '') {
const document = await vscode.workspace.openTextDocument(suggestion.fileUrl)
if (document) {
- const currString = document.getText(
- new vscode.Range(suggestion.startPosition, suggestion.endPosition)
- )
+ currString = document.getText(new vscode.Range(suggestion.startPosition, suggestion.endPosition))
percentage = this.checkDiff(currString, suggestion.originalString)
}
}
} catch (e) {
getLogger().verbose(`Exception Thrown from CodeWhispererTracker: ${e}`)
+ return
} finally {
if ('conversationID' in suggestion) {
const event: AmazonqModifyCode = {
@@ -121,7 +122,7 @@ export class CodeWhispererTracker {
conversationId: event.cwsprChatConversationId,
messageId: event.cwsprChatMessageId,
modificationPercentage: event.cwsprChatModificationPercentage,
- customizationArn: undefinedIfEmpty(getSelectedCustomization().arn),
+ customizationArn: customizationArn,
},
},
})
@@ -137,13 +138,42 @@ export class CodeWhispererTracker {
codewhispererCompletionType: suggestion.completionType,
codewhispererLanguage: suggestion.language,
credentialStartUrl: AuthUtil.instance.startUrl,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererCharactersAccepted: suggestion.originalString.length,
codewhispererCharactersModified: 0, // TODO: currently we don't have an accurate number for this field with existing implementation
})
- // TODO:
- // Temperary comment out user modification event, need further discussion on how to calculate this metric
- // TelemetryHelper.instance.sendUserModificationEvent(suggestion, percentage)
+
+ codeWhispererClient
+ .sendTelemetryEvent({
+ telemetryEvent: {
+ userModificationEvent: {
+ sessionId: suggestion.sessionId,
+ requestId: suggestion.requestId,
+ programmingLanguage: { languageName: suggestion.language },
+ // deprecated % value and should not be used by service side
+ modificationPercentage: percentage,
+ customizationArn: customizationArn,
+ timestamp: new Date(),
+ acceptedCharacterCount: suggestion.originalString.length,
+ unmodifiedAcceptedCharacterCount: getUnmodifiedAcceptedTokens(
+ suggestion.originalString,
+ currString
+ ),
+ },
+ },
+ })
+ .then()
+ .catch((error) => {
+ let requestId: string | undefined
+ if (isAwsError(error)) {
+ requestId = error.requestId
+ }
+
+ getLogger().debug(
+ `Failed to send UserModificationEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${
+ error.message
+ }`
+ )
+ })
}
}
}
diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts
index c4d2c0109b8..ed2dfd66e6c 100644
--- a/packages/core/src/codewhisperer/util/authUtil.ts
+++ b/packages/core/src/codewhisperer/util/authUtil.ts
@@ -59,11 +59,8 @@ export const isValidCodeWhispererCoreConnection = (conn?: Connection): conn is C
return isIamConnection(conn)
}
- if (isSageMaker()) {
- return isIamConnection(conn)
- }
-
return (
+ (isSageMaker() && isIamConnection(conn)) ||
(isCloud9('codecatalyst') && isIamConnection(conn)) ||
(isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes))
)
@@ -71,9 +68,10 @@ export const isValidCodeWhispererCoreConnection = (conn?: Connection): conn is C
/** Superset that includes all of CodeWhisperer + Amazon Q */
export const isValidAmazonQConnection = (conn?: Connection): conn is Connection => {
return (
- (isSsoConnection(conn) || isBuilderIdConnection(conn)) &&
- isValidCodeWhispererCoreConnection(conn) &&
- hasScopes(conn, amazonQScopes)
+ (isSageMaker() && isIamConnection(conn)) ||
+ ((isSsoConnection(conn) || isBuilderIdConnection(conn)) &&
+ isValidCodeWhispererCoreConnection(conn) &&
+ hasScopes(conn, amazonQScopes))
)
}
@@ -442,7 +440,8 @@ export class AuthUtil {
if (conn === undefined) {
return buildFeatureAuthState(AuthStates.disconnected)
}
- if (!isSsoConnection(conn)) {
+
+ if (!isSsoConnection(conn) && !isSageMaker()) {
throw new ToolkitError(`Connection "${conn.id}" is not a valid type: ${conn.type}`)
}
@@ -453,7 +452,7 @@ export class AuthUtil {
return state
}
- if (isBuilderIdConnection(conn) || isIdcSsoConnection(conn)) {
+ if (isBuilderIdConnection(conn) || isIdcSsoConnection(conn) || isSageMaker()) {
if (isValidCodeWhispererCoreConnection(conn)) {
state[Features.codewhispererCore] = AuthStates.connected
}
diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts
index d6c06ef5350..e5daae22d17 100644
--- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts
+++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts
@@ -12,7 +12,7 @@ import {
} from '../../shared/telemetry/telemetry.gen'
import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendation } from '../client/codewhisperer'
import { Position } from 'vscode'
-import { CodeWhispererSupplementalContext } from '../models/model'
+import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model'
class CodeWhispererSession {
static #instance: CodeWhispererSession
@@ -41,6 +41,9 @@ class CodeWhispererSession {
fetchCredentialStartTime = 0
sdkApiCallStartTime = 0
invokeSuggestionStartTime = 0
+ timeToFirstRecommendation = 0
+ firstSuggestionShowTime = 0
+ perceivedLatency = 0
public static get instance() {
return (this.#instance ??= new CodeWhispererSession())
@@ -58,6 +61,12 @@ class CodeWhispererSession {
}
}
+ setTimeToFirstRecommendation(timeToFirstRecommendation: number) {
+ if (this.invokeSuggestionStartTime) {
+ this.timeToFirstRecommendation = timeToFirstRecommendation - this.invokeSuggestionStartTime
+ }
+ }
+
setSuggestionState(index: number, value: string) {
this.suggestionStates.set(index, value)
}
@@ -75,6 +84,25 @@ class CodeWhispererSession {
return this.completionTypes.get(index) || 'Line'
}
+ getPerceivedLatency(triggerType: CodewhispererTriggerType) {
+ if (triggerType === 'OnDemand') {
+ return this.timeToFirstRecommendation
+ } else {
+ return session.firstSuggestionShowTime - vsCodeState.lastUserModificationTime
+ }
+ }
+
+ setPerceivedLatency() {
+ if (this.perceivedLatency !== 0) {
+ return
+ }
+ if (this.triggerType === 'OnDemand') {
+ this.perceivedLatency = this.timeToFirstRecommendation
+ } else {
+ this.perceivedLatency = this.firstSuggestionShowTime - vsCodeState.lastUserModificationTime
+ }
+ }
+
reset() {
this.sessionId = ''
this.requestContext = { request: {} as any, supplementalMetadata: {} as any }
diff --git a/packages/core/src/codewhisperer/util/commonUtil.ts b/packages/core/src/codewhisperer/util/commonUtil.ts
index 05fe21458a7..1d624e77b5e 100644
--- a/packages/core/src/codewhisperer/util/commonUtil.ts
+++ b/packages/core/src/codewhisperer/util/commonUtil.ts
@@ -5,9 +5,14 @@
import * as vscode from 'vscode'
import * as semver from 'semver'
+import { distance } from 'fastest-levenshtein'
import { isCloud9 } from '../../shared/extensionUtilities'
import { getInlineSuggestEnabled } from '../../shared/utilities/editorUtilities'
-import { AWSTemplateCaseInsensitiveKeyWords, AWSTemplateKeyWords } from '../models/constants'
+import {
+ AWSTemplateCaseInsensitiveKeyWords,
+ AWSTemplateKeyWords,
+ JsonConfigFileNamingConvention,
+} from '../models/constants'
export function getLocalDatetime() {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
@@ -61,13 +66,23 @@ export function getPrefixSuffixOverlap(firstString: string, secondString: string
return secondString.slice(0, i)
}
-export function checkLeftContextKeywordsForJsonAndYaml(leftFileContent: string, language: string): boolean {
+export function checkLeftContextKeywordsForJson(fileName: string, leftFileContent: string, language: string): boolean {
if (
- (language === 'json' || language === 'yaml') &&
+ language === 'json' &&
!AWSTemplateKeyWords.some((substring) => leftFileContent.includes(substring)) &&
- !AWSTemplateCaseInsensitiveKeyWords.some((substring) => leftFileContent.toLowerCase().includes(substring))
+ !AWSTemplateCaseInsensitiveKeyWords.some((substring) => leftFileContent.toLowerCase().includes(substring)) &&
+ !JsonConfigFileNamingConvention.has(fileName.toLowerCase())
) {
return true
}
return false
}
+
+// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
+// and thus the unmodified part of recommendation length can be deducted/approximated
+// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
+// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
+// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
+export function getUnmodifiedAcceptedTokens(origin: string, after: string) {
+ return Math.max(origin.length, after.length) - distance(origin, after)
+}
diff --git a/packages/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts
index e81565282f9..72b5d2874eb 100644
--- a/packages/core/src/codewhisperer/util/editorContext.ts
+++ b/packages/core/src/codewhisperer/util/editorContext.ts
@@ -14,7 +14,7 @@ import { fetchSupplementalContext } from './supplementalContext/supplementalCont
import { supplementalContextTimeoutInMs } from '../models/constants'
import { getSelectedCustomization } from './customizationUtil'
import { selectFrom } from '../../shared/utilities/tsUtils'
-import { checkLeftContextKeywordsForJsonAndYaml } from './commonUtil'
+import { checkLeftContextKeywordsForJson } from './commonUtil'
import { CodeWhispererSupplementalContext } from '../models/model'
import { getOptOutPreference } from '../../shared/telemetry/util'
@@ -39,7 +39,7 @@ export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codew
)
)
let languageName = 'plaintext'
- if (!checkLeftContextKeywordsForJsonAndYaml(caretLeftFileContext, editor.document.languageId)) {
+ if (!checkLeftContextKeywordsForJson(document.fileName, caretLeftFileContext, editor.document.languageId)) {
languageName =
runtimeLanguageContext.normalizeLanguage(editor.document.languageId) ?? editor.document.languageId
}
diff --git a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts
index 646581b83d1..a8bfb74175b 100644
--- a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts
+++ b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts
@@ -9,7 +9,7 @@ import { createConstantMap, ConstantMap } from '../../shared/utilities/tsUtils'
import * as codewhispererClient from '../client/codewhisperer'
import * as CodeWhispererConstants from '../models/constants'
-type RuntimeLanguage = Exclude
+type RuntimeLanguage = Exclude | 'systemverilog'
const runtimeLanguageSet: ReadonlySet = new Set([
'c',
@@ -21,15 +21,22 @@ const runtimeLanguageSet: ReadonlySet = new Set([
'kotlin',
'php',
'python',
+ 'powershell',
+ 'r',
+ 'dart',
'ruby',
'rust',
'scala',
'shell',
'sql',
+ 'swift',
+ 'lua',
+ 'vue',
'typescript',
'json',
'yaml',
'tf',
+ 'systemverilog',
])
export class RuntimeLanguageContext {
@@ -93,6 +100,8 @@ export class RuntimeLanguageContext {
r: 'r',
swift: 'swift',
systemVerilog: 'systemVerilog',
+ systemverilog: 'systemVerilog',
+ verilog: 'systemVerilog',
vue: 'vue',
})
this.supportedLanguageExtensionMap = createConstantMap({
@@ -120,6 +129,17 @@ export class RuntimeLanguageContext {
ts: 'typescript',
yaml: 'yaml',
yml: 'yaml',
+ sv: 'systemVerilog',
+ svh: 'systemVerilog',
+ vh: 'systemVerilog',
+ dart: 'dart',
+ lua: 'lua',
+ wlua: 'lua',
+ swift: 'swift',
+ vue: 'vue',
+ ps1: 'powershell',
+ psm1: 'powershell',
+ r: 'r',
})
}
@@ -147,6 +167,9 @@ export class RuntimeLanguageContext {
case 'tsx':
return 'typescript'
+ case 'systemVerilog':
+ return 'systemverilog'
+
default:
if (!runtimeLanguageSet.has(language)) {
getLogger().error(`codewhisperer: unknown runtime language ${language}`)
@@ -242,15 +265,6 @@ export class RuntimeLanguageContext {
case 'plaintext':
return false
- case 'dart':
- case 'lua':
- case 'powershell':
- case 'r':
- case 'swift':
- case 'vue':
- case 'systemVerilog':
- return false
-
default:
return true
}
diff --git a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts
index 44abefed792..1352f3ae8c8 100644
--- a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts
+++ b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts
@@ -4,18 +4,23 @@
*/
import * as vscode from 'vscode'
-import { fs } from '../../../shared'
+import { FeatureConfigProvider, fs } from '../../../shared'
import path = require('path')
import { BM25Document, BM25Okapi } from './rankBm25'
import { ToolkitError } from '../../../shared/errors'
-import { UserGroup, crossFileContextConfig, supplemetalContextFetchingTimeoutMsg } from '../../models/constants'
+import {
+ crossFileContextConfig,
+ supplementalContextTimeoutInMs,
+ supplemetalContextFetchingTimeoutMsg,
+} from '../../models/constants'
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
-import { CodeWhispererUserGroupSettings } from '../userGroupUtil'
import { isTestFile } from './codeParsingUtil'
import { getFileDistance } from '../../../shared/filesystemUtilities'
import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities'
import { getLogger } from '../../../shared/logger/logger'
import { CodeWhispererSupplementalContext, CodeWhispererSupplementalContextItem } from '../../models/model'
+import { LspController } from '../../../amazonq/lsp/lspController'
+import { waitUntil } from '../../../shared/utilities/timeoutUtils'
type CrossFileSupportedLanguage =
| 'java'
@@ -48,24 +53,54 @@ interface Chunk {
score?: number
}
+type SupplementalContextConfig = 'none' | 'v1' | 'v2'
+
export async function fetchSupplementalContextForSrc(
editor: vscode.TextEditor,
cancellationToken: vscode.CancellationToken
): Promise | undefined> {
- const shouldProceed = shouldFetchCrossFileContext(
- editor.document.languageId,
- CodeWhispererUserGroupSettings.instance.userGroup
+ const supplementalContextConfig = getSupplementalContextConfig(editor.document.languageId)
+
+ if (supplementalContextConfig === 'none') {
+ return undefined
+ }
+ if (supplementalContextConfig === 'v1') {
+ return fetchSupplementalContextForSrcV1(editor, cancellationToken)
+ }
+ const promiseV1 = waitUntil(
+ async function () {
+ return await fetchSupplementalContextForSrcV1(editor, cancellationToken)
+ },
+ { timeout: supplementalContextTimeoutInMs, interval: 10, truthy: false }
+ )
+ const promiseV2 = waitUntil(
+ async function () {
+ return await fetchSupplementalContextForSrcV2(editor)
+ },
+ { timeout: supplementalContextTimeoutInMs, interval: 10, truthy: false }
)
+ const [resultV1, resultV2] = await Promise.all([promiseV1, promiseV2])
+ return resultV2 ?? resultV1
+}
+
+export async function fetchSupplementalContextForSrcV2(
+ editor: vscode.TextEditor
+): Promise | undefined> {
+ const inputChunkContent = getInputChunk(editor)
- if (!shouldProceed) {
- return shouldProceed === undefined
- ? undefined
- : {
- supplementalContextItems: [],
- strategy: 'Empty',
- }
+ const inlineProjectContext: { content: string; score: number; filePath: string }[] =
+ await LspController.instance.queryInlineProjectContext(inputChunkContent.content, editor.document.uri.fsPath)
+
+ return {
+ supplementalContextItems: [...inlineProjectContext],
+ strategy: 'LSP',
}
+}
+export async function fetchSupplementalContextForSrcV1(
+ editor: vscode.TextEditor,
+ cancellationToken: vscode.CancellationToken
+): Promise | undefined> {
const codeChunksCalculated = crossFileContextConfig.numberOfChunkToFetch
// Step 1: Get relevant cross files to refer
@@ -91,15 +126,22 @@ export async function fetchSupplementalContextForSrc(
// Step 3: Generate Input chunk (10 lines left of cursor position)
// and Find Best K chunks w.r.t input chunk using BM25
- const inputChunk: Chunk = getInputChunk(editor, crossFileContextConfig.numberOfLinesEachChunk)
+ const inputChunk: Chunk = getInputChunk(editor)
const bestChunks: Chunk[] = findBestKChunkMatches(inputChunk, chunkList, crossFileContextConfig.topK)
throwIfCancelled(cancellationToken)
// Step 4: Transform best chunks to supplemental contexts
const supplementalContexts: CodeWhispererSupplementalContextItem[] = []
+ let totalLength = 0
for (const chunk of bestChunks) {
throwIfCancelled(cancellationToken)
+ totalLength += chunk.nextContent.length
+
+ if (totalLength > crossFileContextConfig.maximumTotalLength) {
+ break
+ }
+
supplementalContexts.push({
filePath: chunk.fileName,
content: chunk.nextContent,
@@ -137,7 +179,8 @@ function findBestKChunkMatches(chunkInput: Chunk, chunkReferences: Chunk[], k: n
/* This extract 10 lines to the left of the cursor from trigger file.
* This will be the inputquery to bm25 matching against list of cross-file chunks
*/
-function getInputChunk(editor: vscode.TextEditor, chunkSize: number) {
+function getInputChunk(editor: vscode.TextEditor) {
+ const chunkSize = crossFileContextConfig.numberOfLinesEachChunk
const cursorPosition = editor.selection.active
const startLine = Math.max(cursorPosition.line - chunkSize, 0)
const endLine = Math.max(cursorPosition.line - 1, 0)
@@ -151,19 +194,17 @@ function getInputChunk(editor: vscode.TextEditor, chunkSize: number) {
/**
* Util to decide if we need to fetch crossfile context since CodeWhisperer CrossFile Context feature is gated by userGroup and language level
* @param languageId: VSCode language Identifier
- * @param userGroup: CodeWhisperer user group settings, refer to userGroupUtil.ts
* @returns specifically returning undefined if the langueage is not supported,
* otherwise true/false depending on if the language is fully supported or not belonging to the user group
*/
-function shouldFetchCrossFileContext(
- languageId: vscode.TextDocument['languageId'],
- userGroup: UserGroup
-): boolean | undefined {
+function getSupplementalContextConfig(languageId: vscode.TextDocument['languageId']): SupplementalContextConfig {
if (!isCrossFileSupported(languageId)) {
- return undefined
+ return 'none'
}
-
- return true
+ if (FeatureConfigProvider.instance.isNewProjectContextGroup()) {
+ return 'v2'
+ }
+ return 'v1'
}
/**
@@ -171,7 +212,7 @@ function shouldFetchCrossFileContext(
* when a given chunk context passes the match in BM25.
* Special handling is needed for last(its next points to its own) and first chunk
*/
-function linkChunks(chunks: Chunk[]) {
+export function linkChunks(chunks: Chunk[]) {
const updatedChunks: Chunk[] = []
// This additional chunk is needed to create a next pointer to chunk 0.
diff --git a/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts b/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts
index 2982df882f1..63c29dc1c9a 100644
--- a/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts
+++ b/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts
@@ -18,8 +18,6 @@ import { ToolkitError } from '../../../shared/errors'
import { supplemetalContextFetchingTimeoutMsg } from '../../models/constants'
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
import { utgConfig } from '../../models/constants'
-import { CodeWhispererUserGroupSettings } from '../userGroupUtil'
-import { UserGroup } from '../../models/constants'
import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities'
import { getLogger } from '../../../shared/logger/logger'
import { CodeWhispererSupplementalContext, CodeWhispererSupplementalContextItem, UtgStrategy } from '../../models/model'
@@ -32,19 +30,12 @@ function isUtgSupportedLanguage(languageId: vscode.TextDocument['languageId']):
return utgSupportedLanguages.includes(languageId)
}
-export function shouldFetchUtgContext(
- languageId: vscode.TextDocument['languageId'],
- userGroup: UserGroup
-): boolean | undefined {
+export function shouldFetchUtgContext(languageId: vscode.TextDocument['languageId']): boolean | undefined {
if (!isUtgSupportedLanguage(languageId)) {
return undefined
}
- if (languageId === 'java') {
- return true
- } else {
- return userGroup === UserGroup.CrossFile
- }
+ return languageId === 'java'
}
/**
@@ -60,10 +51,7 @@ export async function fetchSupplementalContextForTest(
editor: vscode.TextEditor,
cancellationToken: vscode.CancellationToken
): Promise | undefined> {
- const shouldProceed = shouldFetchUtgContext(
- editor.document.languageId,
- CodeWhispererUserGroupSettings.instance.userGroup
- )
+ const shouldProceed = shouldFetchUtgContext(editor.document.languageId)
if (!shouldProceed) {
return shouldProceed === undefined ? undefined : { supplementalContextItems: [], strategy: 'Empty' }
@@ -141,7 +129,7 @@ async function findSourceFileByContent(
throwIfCancelled(cancellationToken)
- testElementList.push(...extractClasses(editor.document.fileName, languageConfig.classExtractionPattern))
+ testElementList.push(...extractClasses(testFileContent, languageConfig.classExtractionPattern))
throwIfCancelled(cancellationToken)
diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts
index 9c5fe6b1856..2d156066fe7 100644
--- a/packages/core/src/codewhisperer/util/telemetryHelper.ts
+++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts
@@ -18,7 +18,6 @@ import {
import { CodewhispererCompletionType, CodewhispererSuggestionState } from '../../shared/telemetry/telemetry'
import { getImportCount } from './importAdderUtil'
import { CodeWhispererSettings } from './codewhispererSettings'
-import { CodeWhispererUserGroupSettings } from './userGroupUtil'
import { getSelectedCustomization } from './customizationUtil'
import { AuthUtil } from './authUtil'
import { isAwsError } from '../../shared/errors'
@@ -31,7 +30,6 @@ import { CodeScanRemediationsEventType } from '../client/codewhispereruserclient
export class TelemetryHelper {
// Some variables for client component latency
private sdkApiCallEndTime = 0
- private firstSuggestionShowTime = 0
private allPaginationEndTime = 0
private firstResponseRequestId = ''
// variables for user trigger decision
@@ -42,8 +40,6 @@ export class TelemetryHelper {
private typeAheadLength = 0
private timeSinceLastModification = 0
private lastTriggerDecisionTime = 0
- private invocationTime = 0
- private timeToFirstRecommendation = 0
private classifierResult?: number = undefined
private classifierThreshold?: number = undefined
// variables for tracking end to end sessions
@@ -90,7 +86,6 @@ export class TelemetryHelper {
codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg,
codewhispererSupplementalContextLatency: supplementalContextMetadata?.latency,
codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererCustomizationArn: getSelectedCustomization().arn,
traceId: this.traceId,
}
@@ -117,7 +112,6 @@ export class TelemetryHelper {
codewhispererLanguage: language,
codewhispererGettingStartedTask: session.taskType,
credentialStartUrl: AuthUtil.instance.startUrl,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout,
codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg,
codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength,
@@ -171,7 +165,6 @@ export class TelemetryHelper {
codewhispererLanguage: session.language,
codewhispererGettingStartedTask: session.taskType,
credentialStartUrl: AuthUtil.instance.startUrl,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout,
codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg,
codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength,
@@ -235,7 +228,6 @@ export class TelemetryHelper {
.map((e) => e.codewhispererSuggestionImportCount || 0)
.reduce((a, b) => a + b, 0),
codewhispererTypeaheadLength: 0,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout,
codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg,
codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength,
@@ -290,13 +282,12 @@ export class TelemetryHelper {
codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime
? performance.now() - this.lastTriggerDecisionTime
: undefined,
- codewhispererTimeToFirstRecommendation: this.timeToFirstRecommendation,
+ codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation,
codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined,
codewhispererSuggestionState: aggregatedSuggestionState,
codewhispererPreviousSuggestionState: this.prevTriggerDecision,
codewhispererClassifierResult: this.classifierResult,
codewhispererClassifierThreshold: this.classifierThreshold,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout,
codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg,
codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength,
@@ -311,11 +302,11 @@ export class TelemetryHelper {
this.prevTriggerDecision = this.getAggregatedSuggestionState(this.sessionDecisions)
this.lastTriggerDecisionTime = performance.now()
- // When we send a userTriggerDecision of Empty or Discard, we set the time users see the first
- // suggestion to be now.
- let e2eLatency = this.firstSuggestionShowTime - session.invokeSuggestionStartTime
- if (e2eLatency < 0) {
- e2eLatency = performance.now() - session.invokeSuggestionStartTime
+ // When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value
+ // and client side will set this value to 0.0.
+ let e2eLatency = session.firstSuggestionShowTime - session.invokeSuggestionStartTime
+ if (aggregatedSuggestionState !== 'Reject' && aggregatedSuggestionState !== 'Accept') {
+ e2eLatency = 0.0
}
client
@@ -333,11 +324,13 @@ export class TelemetryHelper {
completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType),
suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState),
recommendationLatencyMilliseconds: e2eLatency,
+ triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation,
+ perceivedLatencyMilliseconds: session.perceivedLatency,
timestamp: new Date(Date.now()),
- triggerToResponseLatencyMilliseconds: this.timeToFirstRecommendation,
suggestionReferenceCount: referenceCount,
generatedLine: generatedLines,
numberOfRecommendations: suggestionCount,
+ acceptedCharacterCount: acceptedRecommendationContent.length,
},
},
})
@@ -383,16 +376,6 @@ export class TelemetryHelper {
this.timeSinceLastModification = timeSinceLastModification
}
- public setInvocationStartTime(invocationTime: number) {
- this.invocationTime = invocationTime
- }
-
- public setTimeToFirstRecommendation(timeToFirstRecommendation: number) {
- if (this.invocationTime) {
- this.timeToFirstRecommendation = timeToFirstRecommendation - this.invocationTime
- }
- }
-
public setTraceId(traceId: string) {
this.traceId = traceId
}
@@ -402,7 +385,8 @@ export class TelemetryHelper {
this.triggerChar = ''
this.typeAheadLength = 0
this.timeSinceLastModification = 0
- this.timeToFirstRecommendation = 0
+ session.timeToFirstRecommendation = 0
+ session.perceivedLatency = 0
this.classifierResult = undefined
this.classifierThreshold = undefined
}
@@ -485,7 +469,7 @@ export class TelemetryHelper {
session.sdkApiCallStartTime = 0
this.sdkApiCallEndTime = 0
session.fetchCredentialStartTime = 0
- this.firstSuggestionShowTime = 0
+ session.firstSuggestionShowTime = 0
this.allPaginationEndTime = 0
this.firstResponseRequestId = ''
}
@@ -509,8 +493,8 @@ export class TelemetryHelper {
}
public setFirstSuggestionShowTime() {
- if (this.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) {
- this.firstSuggestionShowTime = performance.now()
+ if (session.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) {
+ session.firstSuggestionShowTime = performance.now()
}
}
@@ -523,23 +507,22 @@ export class TelemetryHelper {
// report client component latency after all pagination call finish
// and at least one suggestion is shown to the user
public tryRecordClientComponentLatency() {
- if (this.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) {
+ if (session.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) {
return
}
telemetry.codewhisperer_clientComponentLatency.emit({
codewhispererRequestId: this.firstResponseRequestId,
codewhispererSessionId: session.sessionId,
codewhispererFirstCompletionLatency: this.sdkApiCallEndTime - session.sdkApiCallStartTime,
- codewhispererEndToEndLatency: this.firstSuggestionShowTime - session.invokeSuggestionStartTime,
+ codewhispererEndToEndLatency: session.firstSuggestionShowTime - session.invokeSuggestionStartTime,
codewhispererAllCompletionsLatency: this.allPaginationEndTime - session.sdkApiCallStartTime,
- codewhispererPostprocessingLatency: this.firstSuggestionShowTime - this.sdkApiCallEndTime,
+ codewhispererPostprocessingLatency: session.firstSuggestionShowTime - this.sdkApiCallEndTime,
codewhispererCredentialFetchingLatency: session.sdkApiCallStartTime - session.fetchCredentialStartTime,
codewhispererPreprocessingLatency: session.fetchCredentialStartTime - session.invokeSuggestionStartTime,
codewhispererCompletionType: 'Line',
codewhispererTriggerType: session.triggerType,
codewhispererLanguage: session.language,
credentialStartUrl: AuthUtil.instance.startUrl,
- codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(),
codewhispererCustomizationArn: getSelectedCustomization().arn,
})
}
diff --git a/packages/core/src/codewhisperer/util/zipUtil.ts b/packages/core/src/codewhisperer/util/zipUtil.ts
index f8656536ad8..b0c22eed885 100644
--- a/packages/core/src/codewhisperer/util/zipUtil.ts
+++ b/packages/core/src/codewhisperer/util/zipUtil.ts
@@ -9,7 +9,7 @@ import { tempDirPath } from '../../shared/filesystemUtilities'
import { getLogger } from '../../shared/logger'
import * as CodeWhispererConstants from '../models/constants'
import { ToolkitError } from '../../shared/errors'
-import { fs } from '../../shared'
+import { fs } from '../../shared/fs/fs'
import { getLoggerForScope } from '../service/securityScanHandler'
import { runtimeLanguageContext } from './runtimeLanguageContext'
import { CodewhispererLanguage } from '../../shared/telemetry/telemetry.gen'
diff --git a/packages/core/src/codewhisperer/views/lineAnnotationController.ts b/packages/core/src/codewhisperer/views/lineAnnotationController.ts
index a75ec39a7e9..df00b6fe7ac 100644
--- a/packages/core/src/codewhisperer/views/lineAnnotationController.ts
+++ b/packages/core/src/codewhisperer/views/lineAnnotationController.ts
@@ -46,6 +46,7 @@ function fromId(id: string | undefined): AnnotationState | undefined {
interface AnnotationState {
id: string
suppressWhileRunning: boolean
+ decorationRenderOptions?: vscode.ThemableDecorationAttachmentRenderOptions
text: () => string
updateState(changeSource: AnnotationChangeSource, force: boolean): AnnotationState | undefined
@@ -199,6 +200,26 @@ export class EndState implements AnnotationState {
}
}
+export class InlineChatState implements AnnotationState {
+ static static = 'amazonq_annotation_inline_chat'
+ id = InlineChatState.static
+ suppressWhileRunning = false
+
+ text = () => {
+ if (os.platform() === 'darwin') {
+ return 'Amazon Q: Edit \u2318I'
+ } else {
+ return 'Amazon Q: Edit (Ctrl+I)'
+ }
+ }
+ updateState(_changeSource: AnnotationChangeSource, _force: boolean): AnnotationState {
+ return this
+ }
+ isNextState(_state: AnnotationState | undefined): boolean {
+ return false
+ }
+}
+
/**
* There are
* - existing users
@@ -314,6 +335,26 @@ export class LineAnnotationController implements vscode.Disposable {
await globals.globalState.update(inlinehintKey, this._currentState.id)
}
+ /**
+ * Trys to show the inline hint, if the tutorial is not finished it will not be shown
+ */
+ async tryShowInlineHint(): Promise {
+ if (this.isTutorialDone()) {
+ this._isReady = true
+ this._currentState = new InlineChatState()
+ return true
+ }
+ return false
+ }
+
+ async tryHideInlineHint(): Promise {
+ if (this._currentState instanceof InlineChatState) {
+ this._currentState = new EndState()
+ return true
+ }
+ return false
+ }
+
private async onActiveLinesChanged(e: LinesChangeEvent) {
if (!this._isReady) {
return
@@ -438,7 +479,7 @@ export class LineAnnotationController implements vscode.Disposable {
): Partial | undefined {
const isCWRunning = RecommendationService.instance.isRunning
- const textOptions = {
+ const textOptions: vscode.ThemableDecorationAttachmentRenderOptions = {
contentText: '',
fontWeight: 'normal',
fontStyle: 'normal',
diff --git a/packages/core/src/codewhispererChat/app.ts b/packages/core/src/codewhispererChat/app.ts
index f65352ecc10..6781cde30e5 100644
--- a/packages/core/src/codewhispererChat/app.ts
+++ b/packages/core/src/codewhispererChat/app.ts
@@ -10,6 +10,7 @@ import { AmazonQAppInitContext } from '../amazonq/apps/initContext'
import { MessageListener } from '../amazonq/messages/messageListener'
import { MessagePublisher } from '../amazonq/messages/messagePublisher'
import {
+ ViewDiff,
ChatItemFeedbackMessage,
ChatItemVotedMessage,
CopyCodeToClipboard,
@@ -24,6 +25,7 @@ import {
TabCreatedMessage,
TriggerTabIDReceived,
UIFocusMessage,
+ AcceptDiff,
} from './controllers/chat/model'
import { EditorContextCommand, registerCommands } from './commands/registerCommands'
@@ -34,6 +36,8 @@ export function init(appContext: AmazonQAppInitContext) {
processTabClosedMessage: new EventEmitter(),
processTabChangedMessage: new EventEmitter(),
processInsertCodeAtCursorPosition: new EventEmitter(),
+ processAcceptDiff: new EventEmitter(),
+ processViewDiff: new EventEmitter(),
processCopyCodeToClipboard: new EventEmitter(),
processContextMenuCommand: new EventEmitter(),
processTriggerTabIDReceived: new EventEmitter(),
@@ -62,6 +66,8 @@ export function init(appContext: AmazonQAppInitContext) {
processInsertCodeAtCursorPosition: new MessageListener(
cwChatControllerEventEmitters.processInsertCodeAtCursorPosition
),
+ processAcceptDiff: new MessageListener(cwChatControllerEventEmitters.processAcceptDiff),
+ processViewDiff: new MessageListener(cwChatControllerEventEmitters.processViewDiff),
processCopyCodeToClipboard: new MessageListener(
cwChatControllerEventEmitters.processCopyCodeToClipboard
),
@@ -108,6 +114,8 @@ export function init(appContext: AmazonQAppInitContext) {
processInsertCodeAtCursorPosition: new MessagePublisher(
cwChatControllerEventEmitters.processInsertCodeAtCursorPosition
),
+ processAcceptDiff: new MessagePublisher(cwChatControllerEventEmitters.processAcceptDiff),
+ processViewDiff: new MessagePublisher(cwChatControllerEventEmitters.processViewDiff),
processCopyCodeToClipboard: new MessagePublisher(
cwChatControllerEventEmitters.processCopyCodeToClipboard
),
diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts
index 00326ddd955..fc164ebb95c 100644
--- a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts
+++ b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts
@@ -3,10 +3,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client'
import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming'
import * as vscode from 'vscode'
import { ToolkitError } from '../../../../shared/errors'
import { createCodeWhispererChatStreamingClient } from '../../../../shared/clients/codewhispererChatClient'
+import { createQDeveloperStreamingClient } from '../../../../shared/clients/qDeveloperChatClient'
export class ChatSession {
private sessionId?: string
@@ -28,8 +30,28 @@ export class ChatSession {
public setSessionID(id?: string) {
this.sessionId = id
}
+ async chatIam(chatRequest: SendMessageRequest): Promise {
+ const client = await createQDeveloperStreamingClient()
- async chat(chatRequest: GenerateAssistantResponseRequest): Promise {
+ const response = await client.sendMessage(chatRequest)
+ if (!response.sendMessageResponse) {
+ throw new ToolkitError(
+ `Empty chat response. Session id: ${this.sessionId} Request ID: ${response.$metadata.requestId}`
+ )
+ }
+
+ const responseStream = response.sendMessageResponse
+ for await (const event of responseStream) {
+ if ('messageMetadataEvent' in event) {
+ this.sessionId = event.messageMetadataEvent?.conversationId
+ break
+ }
+ }
+
+ return response
+ }
+
+ async chatSso(chatRequest: GenerateAssistantResponseRequest): Promise {
const client = await createCodeWhispererChatStreamingClient()
if (this.sessionId !== undefined && chatRequest.conversationState !== undefined) {
diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts
index f853fb4611d..9ff56523379 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts
@@ -4,14 +4,14 @@
*/
import {
+ ConversationState,
CursorState,
DocumentSymbol,
- GenerateAssistantResponseRequest,
RelevantTextDocument,
SymbolType,
TextDocument,
} from '@amzn/codewhisperer-streaming'
-import { TriggerPayload } from '../model'
+import { ChatTriggerType, TriggerPayload } from '../model'
import { undefinedIfEmpty } from '../../../../shared'
const fqnNameSizeDownLimit = 1
@@ -37,7 +37,7 @@ export const supportedLanguagesList = [
const filePathSizeLimit = 4_000
const customerMessageSizeLimit = 4_000
-export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): GenerateAssistantResponseRequest {
+export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } {
let document: TextDocument | undefined = undefined
let cursorState: CursorState | undefined = undefined
@@ -98,6 +98,7 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): Gen
const useRelevantDocuments = triggerPayload.useRelevantDocuments
// service will throw validation exception if string is empty
const customizationArn: string | undefined = undefinedIfEmpty(triggerPayload.customization.arn)
+ const chatTriggerType = triggerPayload.trigger === ChatTriggerType.InlineChatMessage ? 'INLINE_CHAT' : 'MANUAL'
return {
conversationState: {
@@ -117,7 +118,7 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): Gen
userIntent: triggerPayload.userIntent,
},
},
- chatTriggerType: 'MANUAL',
+ chatTriggerType,
customizationArn: customizationArn,
},
}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts
index e4680c02571..5f527ffa4ba 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts
@@ -5,7 +5,7 @@
import { Event as VSCodeEvent, Uri } from 'vscode'
import { EditorContextExtractor } from '../../editor/context/extractor'
import { ChatSessionStorage } from '../../storages/chatSession'
-import { Messenger, StaticTextResponseType } from './messenger/messenger'
+import { Messenger, MessengerResponseType, StaticTextResponseType } from './messenger/messenger'
import {
PromptMessage,
ChatTriggerType,
@@ -24,6 +24,8 @@ import {
ResponseBodyLinkClickMessage,
ChatPromptCommandType,
FooterInfoLinkClick,
+ ViewDiff,
+ AcceptDiff,
} from './model'
import { AppToWebViewMessageDispatcher } from '../../view/connector/connector'
import { MessagePublisher } from '../../../amazonq/messages/messagePublisher'
@@ -32,10 +34,8 @@ import { EditorContentController } from '../../../amazonq/commons/controllers/co
import { EditorContextCommand } from '../../commands/registerCommands'
import { PromptsGenerator } from './prompts/promptsGenerator'
import { TriggerEventsStorage } from '../../storages/triggerEvents'
-import {
- CodeWhispererStreamingServiceException,
- GenerateAssistantResponseCommandOutput,
-} from '@amzn/codewhisperer-streaming'
+import { SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client'
+import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming'
import { UserIntentRecognizer } from './userIntent/userIntentRecognizer'
import { CWCTelemetryHelper, recordTelemetryChatRunCommand } from './telemetryHelper'
import { CodeWhispererTracker } from '../../../codewhisperer/tracker/codewhispererTracker'
@@ -52,6 +52,7 @@ import { getHttpStatusCode, AwsClientResponseError } from '../../../shared/error
import { uiEventRecorder } from '../../../amazonq/util/eventRecorder'
import { globals } from '../../../shared'
import { telemetry } from '../../../shared/telemetry'
+import { isSsoConnection } from '../../../auth/connection'
export interface ChatControllerMessagePublishers {
readonly processPromptChatMessage: MessagePublisher
@@ -59,6 +60,8 @@ export interface ChatControllerMessagePublishers {
readonly processTabClosedMessage: MessagePublisher
readonly processTabChangedMessage: MessagePublisher
readonly processInsertCodeAtCursorPosition: MessagePublisher
+ readonly processAcceptDiff: MessagePublisher
+ readonly processViewDiff: MessagePublisher
readonly processCopyCodeToClipboard: MessagePublisher
readonly processContextMenuCommand: MessagePublisher
readonly processTriggerTabIDReceived: MessagePublisher
@@ -77,6 +80,8 @@ export interface ChatControllerMessageListeners {
readonly processTabClosedMessage: MessageListener
readonly processTabChangedMessage: MessageListener
readonly processInsertCodeAtCursorPosition: MessageListener
+ readonly processAcceptDiff: MessageListener
+ readonly processViewDiff: MessageListener
readonly processCopyCodeToClipboard: MessageListener
readonly processContextMenuCommand: MessageListener
readonly processTriggerTabIDReceived: MessageListener
@@ -159,6 +164,14 @@ export class ChatController {
return this.processInsertCodeAtCursorPosition(data)
})
+ this.chatControllerMessageListeners.processAcceptDiff.onMessage((data) => {
+ return this.processAcceptDiff(data)
+ })
+
+ this.chatControllerMessageListeners.processViewDiff.onMessage((data) => {
+ return this.processViewDiff(data)
+ })
+
this.chatControllerMessageListeners.processCopyCodeToClipboard.onMessage((data) => {
return this.processCopyCodeToClipboard(data)
})
@@ -278,6 +291,30 @@ export class ChatController {
this.telemetryHelper.recordInteractWithMessage(message)
}
+ private async processAcceptDiff(message: AcceptDiff) {
+ const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || ''
+ this.editorContentController
+ .acceptDiff({ ...message, ...context })
+ .then(() => {
+ this.telemetryHelper.recordInteractWithMessage(message)
+ })
+ .catch((error) => {
+ this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' })
+ })
+ }
+
+ private async processViewDiff(message: ViewDiff) {
+ const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || ''
+ this.editorContentController
+ .viewDiff({ ...message, ...context })
+ .then(() => {
+ this.telemetryHelper.recordInteractWithMessage(message)
+ })
+ .catch((error) => {
+ this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' })
+ })
+ }
+
private async processCopyCodeToClipboard(message: CopyCodeToClipboard) {
this.telemetryHelper.recordInteractWithMessage(message)
}
@@ -618,11 +655,24 @@ export class ChatController {
request
)}`
)
- let response: GenerateAssistantResponseCommandOutput | undefined = undefined
+ let response: MessengerResponseType | undefined = undefined
session.createNewTokenSource()
try {
this.messenger.sendInitalStream(tabID, triggerID)
- response = await session.chat(request)
+ this.telemetryHelper.setConversationStreamStartTime(tabID)
+ if (isSsoConnection(AuthUtil.instance.conn)) {
+ const { $metadata, generateAssistantResponseResponse } = await session.chatSso(request)
+ response = {
+ $metadata: $metadata,
+ message: generateAssistantResponseResponse,
+ }
+ } else {
+ const { $metadata, sendMessageResponse } = await session.chatIam(request as SendMessageRequest)
+ response = {
+ $metadata: $metadata,
+ message: sendMessageResponse,
+ }
+ }
this.telemetryHelper.recordEnterFocusConversation(triggerEvent.tabID)
this.telemetryHelper.recordStartConversation(triggerEvent, triggerPayload)
diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts
index dad3181381d..3c377119747 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts
@@ -13,9 +13,10 @@ import {
QuickActionMessage,
} from '../../../view/connector/connector'
import { EditorContextCommandType } from '../../../commands/registerCommands'
+import { ChatResponseStream as qdevChatResponseStream } from '@amzn/amazon-q-developer-streaming-client'
import {
+ ChatResponseStream as cwChatResponseStream,
CodeWhispererStreamingServiceException,
- GenerateAssistantResponseCommandOutput,
SupplementaryWebLink,
} from '@amzn/codewhisperer-streaming'
import { ChatMessage, ErrorMessage, FollowUp, Suggestion } from '../../../view/connector/connector'
@@ -27,15 +28,21 @@ import { getHttpStatusCode, getRequestId, ToolkitError } from '../../../../share
import { keys } from '../../../../shared/utilities/tsUtils'
import { getLogger } from '../../../../shared/logger/logger'
import { FeatureAuthState } from '../../../../codewhisperer/util/authUtil'
-import { AuthFollowUpType, AuthMessageDataMap } from '../../../../amazonq/auth/model'
import { userGuideURL } from '../../../../amazonq/webview/ui/texts/constants'
import { CodeScanIssue } from '../../../../codewhisperer/models/model'
import { marked } from 'marked'
import { JSDOM } from 'jsdom'
import { LspController } from '../../../../amazonq/lsp/lspController'
+import { extractCodeBlockLanguage } from '../../../../shared/markdown'
+import { extractAuthFollowUp } from '../../../../amazonq/util/authUtils'
export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help'
+export type MessengerResponseType = {
+ $metadata: { requestId?: string; httpStatusCode?: number }
+ message?: AsyncIterable
+}
+
export class Messenger {
public constructor(
private readonly dispatcher: AppToWebViewMessageDispatcher,
@@ -43,26 +50,7 @@ export class Messenger {
) {}
public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string, triggerID: string) {
- let authType: AuthFollowUpType = 'full-auth'
- let message = AuthMessageDataMap[authType].message
- if (
- credentialState.codewhispererChat === 'disconnected' &&
- credentialState.codewhispererCore === 'disconnected'
- ) {
- authType = 'full-auth'
- message = AuthMessageDataMap[authType].message
- }
-
- if (credentialState.codewhispererCore === 'connected' && credentialState.codewhispererChat === 'expired') {
- authType = 'missing_scopes'
- message = AuthMessageDataMap[authType].message
- }
-
- if (credentialState.codewhispererChat === 'expired' && credentialState.codewhispererCore === 'expired') {
- authType = 're-auth'
- message = AuthMessageDataMap[authType].message
- }
-
+ const { message, authType } = extractAuthFollowUp(credentialState)
this.dispatcher.sendAuthNeededExceptionMessage(
new AuthNeededException(
{
@@ -86,6 +74,8 @@ export class Messenger {
relatedSuggestions: undefined,
triggerID,
messageID: '',
+ userIntent: undefined,
+ codeBlockLanguage: undefined,
},
tabID
)
@@ -119,7 +109,7 @@ export class Messenger {
}
public async sendAIResponse(
- response: GenerateAssistantResponseCommandOutput,
+ response: MessengerResponseType,
session: ChatSession,
tabID: string,
triggerID: string,
@@ -130,8 +120,9 @@ export class Messenger {
let codeReference: CodeReference[] = []
let followUps: FollowUp[] = []
let relatedSuggestions: Suggestion[] = []
+ let codeBlockLanguage: string = 'plaintext'
- if (response.generateAssistantResponseResponse === undefined) {
+ if (response.message === undefined) {
throw new ToolkitError(
`Empty response from CodeWhisperer Streaming service. Request ID: ${response.$metadata.requestId}`
)
@@ -148,7 +139,7 @@ export class Messenger {
const eventCounts = new Map()
waitUntil(
async () => {
- for await (const chatEvent of response.generateAssistantResponseResponse!) {
+ for await (const chatEvent of response.message!) {
for (const key of keys(chatEvent)) {
if ((chatEvent[key] as any) !== undefined) {
eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1)
@@ -181,7 +172,9 @@ export class Messenger {
chatEvent.assistantResponseEvent.content.length > 0
) {
message += chatEvent.assistantResponseEvent.content
-
+ if (codeBlockLanguage === 'plaintext') {
+ codeBlockLanguage = extractCodeBlockLanguage(message)
+ }
this.dispatcher.sendChatMessage(
new ChatMessage(
{
@@ -193,6 +186,8 @@ export class Messenger {
codeReference,
triggerID,
messageID,
+ userIntent: triggerPayload.userIntent,
+ codeBlockLanguage: codeBlockLanguage,
},
tabID
)
@@ -269,6 +264,8 @@ export class Messenger {
relatedSuggestions: undefined,
triggerID,
messageID,
+ userIntent: triggerPayload.userIntent,
+ codeBlockLanguage: codeBlockLanguage,
},
tabID
)
@@ -286,6 +283,8 @@ export class Messenger {
relatedSuggestions,
triggerID,
messageID,
+ userIntent: triggerPayload.userIntent,
+ codeBlockLanguage: undefined,
},
tabID
)
@@ -302,6 +301,8 @@ export class Messenger {
relatedSuggestions: undefined,
triggerID,
messageID,
+ userIntent: triggerPayload.userIntent,
+ codeBlockLanguage: undefined,
},
tabID
)
@@ -425,6 +426,8 @@ export class Messenger {
relatedSuggestions: undefined,
triggerID,
messageID: 'static_message_' + triggerID,
+ userIntent: undefined,
+ codeBlockLanguage: undefined,
},
tabID
)
@@ -435,7 +438,7 @@ export class Messenger {
let message = ''
switch (quickAction) {
case 'help':
- message = 'What can Amazon Q help me with?'
+ message = 'How can Amazon Q help me?'
break
}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts
index 94e7d0d10cc..f79510acacb 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/model.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts
@@ -46,6 +46,7 @@ export interface InsertCodeAtCursorPosition {
eventId: string
codeBlockIndex: number
totalCodeBlocks: number
+ codeBlockLanguage: string
}
export interface CopyCodeToClipboard {
@@ -59,6 +60,32 @@ export interface CopyCodeToClipboard {
eventId: string
codeBlockIndex: number
totalCodeBlocks: number
+ codeBlockLanguage: string
+}
+
+export interface AcceptDiff {
+ command: string | undefined
+ tabID: string // rename tabId
+ messageId: string
+ actionId: string
+ data: string
+ code: string
+ referenceTrackerInformation?: CodeReference[]
+ eventId: string
+ codeBlockIndex?: number
+ totalCodeBlocks?: number
+}
+export interface ViewDiff {
+ command: string | undefined
+ tabID: string // rename tabId
+ messageId: string
+ actionId: string
+ data: string
+ code: string
+ referenceTrackerInformation?: CodeReference[]
+ eventId: string
+ codeBlockIndex?: number
+ totalCodeBlocks?: number
}
export type ChatPromptCommandType =
@@ -129,6 +156,7 @@ export interface ChatItemFeedbackMessage {
export enum ChatTriggerType {
ChatMessage = 'ChatMessage',
+ InlineChatMessage = 'InlineChatMessage',
}
export interface TriggerPayload {
diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts
index a86d8c0ff68..c6e12d601c2 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts
@@ -15,6 +15,8 @@ import {
} from '../../../shared/telemetry/telemetry'
import { ChatSessionStorage } from '../../storages/chatSession'
import {
+ AcceptDiff,
+ ViewDiff,
ChatItemFeedbackMessage,
ChatItemVotedMessage,
CopyCodeToClipboard,
@@ -63,6 +65,8 @@ export class CWCTelemetryHelper {
private triggerEventsStorage: TriggerEventsStorage
private responseStreamStartTime: Map = new Map()
private responseStreamTotalTime: Map = new Map()
+ private conversationStreamStartTime: Map = new Map()
+ private conversationStreamTotalTime: Map = new Map()
private responseStreamTimeForChunks: Map = new Map()
private responseWithProjectContext: Map = new Map()
@@ -171,6 +175,7 @@ export class CWCTelemetryHelper {
public recordInteractWithMessage(
message:
+ | AcceptDiff
| InsertCodeAtCursorPosition
| CopyCodeToClipboard
| PromptMessage
@@ -178,6 +183,8 @@ export class CWCTelemetryHelper {
| SourceLinkClickMessage
| ResponseBodyLinkClickMessage
| FooterInfoLinkClick
+ | ViewDiff,
+ { result }: { result: Result } = { result: 'Succeeded' }
) {
const conversationId = this.getConversationId(message.tabID)
let event: AmazonqInteractWithMessage | undefined
@@ -185,7 +192,7 @@ export class CWCTelemetryHelper {
case 'insert_code_at_cursor_position':
message = message as InsertCodeAtCursorPosition
event = {
- result: 'Succeeded',
+ result,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
cwsprChatMessageId: message.messageId,
@@ -198,12 +205,13 @@ export class CWCTelemetryHelper {
cwsprChatCodeBlockIndex: message.codeBlockIndex,
cwsprChatTotalCodeBlocks: message.totalCodeBlocks,
cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId),
+ cwsprChatProgrammingLanguage: message.codeBlockLanguage,
}
break
case 'code_was_copied_to_clipboard':
message = message as CopyCodeToClipboard
event = {
- result: 'Succeeded',
+ result,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
cwsprChatMessageId: message.messageId,
@@ -215,12 +223,45 @@ export class CWCTelemetryHelper {
cwsprChatCodeBlockIndex: message.codeBlockIndex,
cwsprChatTotalCodeBlocks: message.totalCodeBlocks,
cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId),
+ cwsprChatProgrammingLanguage: message.codeBlockLanguage,
+ }
+ break
+ case 'accept_diff':
+ message = message as AcceptDiff
+ event = {
+ result,
+ cwsprChatConversationId: conversationId ?? '',
+ cwsprChatMessageId: message.messageId,
+ cwsprChatInteractionType: 'acceptDiff',
+ credentialStartUrl: AuthUtil.instance.startUrl,
+ cwsprChatAcceptedCharactersLength: message.code.length,
+ cwsprChatHasReference:
+ message.referenceTrackerInformation && message.referenceTrackerInformation.length > 0,
+ cwsprChatCodeBlockIndex: message.codeBlockIndex,
+ cwsprChatTotalCodeBlocks: message.totalCodeBlocks,
+ cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId),
+ }
+ break
+ case 'view_diff':
+ message = message as ViewDiff
+ event = {
+ result,
+ cwsprChatConversationId: conversationId ?? '',
+ cwsprChatMessageId: message.messageId,
+ cwsprChatInteractionType: 'viewDiff',
+ credentialStartUrl: AuthUtil.instance.startUrl,
+ cwsprChatAcceptedCharactersLength: message.code.length,
+ cwsprChatHasReference:
+ message.referenceTrackerInformation && message.referenceTrackerInformation.length > 0,
+ cwsprChatCodeBlockIndex: message.codeBlockIndex,
+ cwsprChatTotalCodeBlocks: message.totalCodeBlocks,
+ cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId),
}
break
case 'follow-up-was-clicked':
message = message as PromptMessage
event = {
- result: 'Succeeded',
+ result,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
cwsprChatMessageId: message.messageId,
@@ -231,7 +272,7 @@ export class CWCTelemetryHelper {
case 'chat-item-voted':
message = message as ChatItemVotedMessage
event = {
- result: 'Succeeded',
+ result,
cwsprChatMessageId: message.messageId,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
@@ -242,7 +283,7 @@ export class CWCTelemetryHelper {
case 'source-link-click':
message = message as SourceLinkClickMessage
event = {
- result: 'Succeeded',
+ result,
cwsprChatMessageId: message.messageId,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
@@ -254,7 +295,7 @@ export class CWCTelemetryHelper {
case 'response-body-link-click':
message = message as ResponseBodyLinkClickMessage
event = {
- result: 'Succeeded',
+ result,
cwsprChatMessageId: message.messageId,
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
@@ -266,7 +307,7 @@ export class CWCTelemetryHelper {
case 'footer-info-link-click':
message = message as FooterInfoLinkClick
event = {
- result: 'Succeeded',
+ result,
cwsprChatMessageId: 'footer',
cwsprChatConversationId: conversationId ?? '',
credentialStartUrl: AuthUtil.instance.startUrl,
@@ -317,6 +358,10 @@ export class CWCTelemetryHelper {
return 'UPVOTE'
case 'downvote':
return 'DOWNVOTE'
+ case 'acceptDiff':
+ return 'ACCEPT_DIFF'
+ case 'viewDiff':
+ return 'VIEW_DIFF'
default:
return 'UNKNOWN'
}
@@ -408,7 +453,9 @@ export class CWCTelemetryHelper {
this.getTimeBetweenChunks(message.tabID, this.responseStreamTimeForChunks)
),
cwsprChatFullResponseLatency: this.responseStreamTotalTime.get(message.tabID) ?? 0,
- cwsprTimeToFirstDisplay: this.getFirstDisplayTime(tabID, startTime),
+ cwsprChatTimeToFirstDisplay: this.getFirstDisplayTime(tabID, startTime),
+ cwsprChatTimeToFirstUsableChunk: this.getFirstUsableChunkTime(message.tabID) ?? 0,
+ cwsprChatFullServerResponseLatency: this.conversationStreamTotalTime.get(message.tabID) ?? 0,
cwsprChatTimeBetweenDisplays: JSON.stringify(this.getTimeBetweenChunks(tabID, this.displayTimeForChunks)),
cwsprChatFullDisplayLatency: fullDisplayLatency,
cwsprChatRequestLength: triggerPayload.message?.length ?? 0,
@@ -510,6 +557,10 @@ export class CWCTelemetryHelper {
this.responseWithProjectContext.set(messageId, true)
}
+ public setConversationStreamStartTime(tabID: string) {
+ this.conversationStreamStartTime.set(tabID, performance.now())
+ }
+
private getResponseStreamTimeToFirstChunk(tabID: string): number {
const chunkTimes = this.responseStreamTimeForChunks.get(tabID) ?? [0, 0]
if (chunkTimes.length === 1) {
@@ -529,6 +580,13 @@ export class CWCTelemetryHelper {
return Math.round(chunkTimes[0] - startTime)
}
+ private getFirstUsableChunkTime(tabID: string) {
+ const startTime = this.conversationStreamStartTime.get(tabID) ?? 0
+ const chunkTimes = this.responseStreamTimeForChunks.get(tabID) ?? [0, 0]
+ // first chunk is the start time, we use the second because thats the first actual usable chunk time
+ return Math.round(chunkTimes[1] - startTime)
+ }
+
private getTimeBetweenChunks(tabID: string, chunks: Map): number[] {
try {
const chunkDeltaTimes: number[] = []
@@ -545,8 +603,13 @@ export class CWCTelemetryHelper {
}
public setResponseStreamTotalTime(tabID: string) {
- const totalTime = performance.now() - (this.responseStreamStartTime.get(tabID) ?? 0)
- this.responseStreamTotalTime.set(tabID, Math.round(totalTime))
+ // time from when the requests started streaming until the end of the stream
+ const totalStreamingTime = performance.now() - (this.responseStreamStartTime.get(tabID) ?? 0)
+ this.responseStreamTotalTime.set(tabID, Math.round(totalStreamingTime))
+
+ // time from the initial server request, including creating the conversation id, until the end of the stream
+ const totalConversationTime = performance.now() - (this.conversationStreamStartTime.get(tabID) ?? 0)
+ this.conversationStreamTotalTime.set(tabID, Math.round(totalConversationTime))
}
public getConversationId(tabID: string): string | undefined {
diff --git a/packages/core/src/codewhispererChat/editor/context/file/languages.ts b/packages/core/src/codewhispererChat/editor/context/file/languages.ts
index b2dac49f2ad..51887eaf31b 100644
--- a/packages/core/src/codewhispererChat/editor/context/file/languages.ts
+++ b/packages/core/src/codewhispererChat/editor/context/file/languages.ts
@@ -5,57 +5,57 @@
import { TextDocument } from 'vscode'
+const defaultLanguages = [
+ 'yaml',
+ 'xsl',
+ 'xml',
+ 'vue',
+ 'tex',
+ 'typescript',
+ 'swift',
+ 'stylus',
+ 'sql',
+ 'slim',
+ 'shaderlab',
+ 'sass',
+ 'rust',
+ 'ruby',
+ 'r',
+ 'python',
+ 'pug',
+ 'powershell',
+ 'php',
+ 'perl',
+ 'markdown',
+ 'makefile',
+ 'lua',
+ 'less',
+ 'latex',
+ 'json',
+ 'javascript',
+ 'java',
+ 'ini',
+ 'html',
+ 'haml',
+ 'handlebars',
+ 'groovy',
+ 'go',
+ 'diff',
+ 'css',
+ 'c',
+ 'coffeescript',
+ 'clojure',
+ 'bibtex',
+ 'abap',
+]
+
export function extractLanguageNameFromFile(file: TextDocument): string | undefined {
const languageId = file.languageId
if (languageId === undefined) {
return undefined
}
- if (
- [
- 'yaml',
- 'xsl',
- 'xml',
- 'vue',
- 'tex',
- 'typescript',
- 'swift',
- 'stylus',
- 'sql',
- 'slim',
- 'shaderlab',
- 'sass',
- 'rust',
- 'ruby',
- 'r',
- 'python',
- 'pug',
- 'powershell',
- 'php',
- 'perl',
- 'markdown',
- 'makefile',
- 'lua',
- 'less',
- 'latex',
- 'json',
- 'javascript',
- 'java',
- 'ini',
- 'html',
- 'haml',
- 'handlebars',
- 'groovy',
- 'go',
- 'diff',
- 'css',
- 'c',
- 'coffeescript',
- 'clojure',
- 'bibtex',
- 'abap',
- ].includes(languageId)
- ) {
+ if (defaultLanguages.includes(languageId)) {
return languageId
}
switch (languageId) {
@@ -117,54 +117,7 @@ export function extractLanguageNameFromFile(file: TextDocument): string | undefi
export function extractAdditionalLanguageMatchPoliciesFromFile(file: TextDocument): Set {
const languageId = file.languageId
- if (languageId === undefined) {
- return new Set()
- }
- if (
- [
- 'yaml',
- 'xsl',
- 'xml',
- 'vue',
- 'tex',
- 'typescript',
- 'swift',
- 'stylus',
- 'sql',
- 'slim',
- 'shaderlab',
- 'sass',
- 'rust',
- 'ruby',
- 'r',
- 'python',
- 'pug',
- 'powershell',
- 'php',
- 'perl',
- 'markdown',
- 'makefile',
- 'lua',
- 'less',
- 'latex',
- 'json',
- 'javascript',
- 'java',
- 'ini',
- 'html',
- 'haml',
- 'handlebars',
- 'groovy',
- 'go',
- 'diff',
- 'css',
- 'c',
- 'coffeescript',
- 'clojure',
- 'bibtex',
- 'abap',
- ].includes(languageId)
- ) {
+ if (languageId === undefined || defaultLanguages.includes(languageId)) {
return new Set()
}
switch (languageId) {
diff --git a/packages/core/src/codewhispererChat/index.ts b/packages/core/src/codewhispererChat/index.ts
index 0a954458cfe..e473203caf5 100644
--- a/packages/core/src/codewhispererChat/index.ts
+++ b/packages/core/src/codewhispererChat/index.ts
@@ -7,3 +7,11 @@ export { FocusAreaContextExtractor } from './editor/context/focusArea/focusAreaE
export { TryChatCodeLensProvider, resolveModifierKey, tryChatCodeLensCommand } from './editor/codelens'
export { focusAmazonQPanel } from './commands/registerCommands'
export { ChatSession } from './clients/chat/v0/chat'
+export { triggerPayloadToChatRequest } from './controllers/chat/chatRequest/converter'
+export { ChatTriggerType, PromptMessage, TriggerPayload } from './controllers/chat/model'
+export { UserIntentRecognizer } from './controllers/chat/userIntent/userIntentRecognizer'
+export { EditorContextExtractor } from './editor/context/extractor'
+export { ChatSessionStorage } from './storages/chatSession'
+export { TriggerEventsStorage } from './storages/triggerEvents'
+export { ReferenceLogController } from './view/messages/referenceLogController'
+export { extractLanguageNameFromFile } from './editor/context/file/languages'
diff --git a/packages/core/src/codewhispererChat/storages/triggerEvents.ts b/packages/core/src/codewhispererChat/storages/triggerEvents.ts
index 2218d4a11a9..d30ebf48939 100644
--- a/packages/core/src/codewhispererChat/storages/triggerEvents.ts
+++ b/packages/core/src/codewhispererChat/storages/triggerEvents.ts
@@ -12,6 +12,7 @@ export type TriggerEventType =
| 'follow_up'
| 'onboarding_page_interaction'
| 'quick_action'
+ | 'inline_chat'
export interface TriggerEvent {
readonly id: string
diff --git a/packages/core/src/codewhispererChat/view/connector/connector.ts b/packages/core/src/codewhispererChat/view/connector/connector.ts
index 094c2b2c6d9..02794af5fb3 100644
--- a/packages/core/src/codewhispererChat/view/connector/connector.ts
+++ b/packages/core/src/codewhispererChat/view/connector/connector.ts
@@ -141,6 +141,8 @@ export interface ChatMessageProps {
readonly codeReference?: CodeReference[]
readonly triggerID: string
readonly messageID: string
+ readonly userIntent: string | undefined
+ readonly codeBlockLanguage: string | undefined
}
export class ChatMessage extends UiMessage {
@@ -153,6 +155,8 @@ export class ChatMessage extends UiMessage {
readonly followUpsHeader: string | undefined
readonly triggerID: string
readonly messageID: string | undefined
+ readonly userIntent: string | undefined
+ readonly codeBlockLanguage: string | undefined
override type = 'chatMessage'
constructor(props: ChatMessageProps, tabID: string) {
@@ -165,6 +169,8 @@ export class ChatMessage extends UiMessage {
this.codeReference = props.codeReference
this.triggerID = props.triggerID
this.messageID = props.messageID
+ this.userIntent = props.userIntent
+ this.codeBlockLanguage = props.codeBlockLanguage
}
}
diff --git a/packages/core/src/codewhispererChat/view/messages/messageListener.ts b/packages/core/src/codewhispererChat/view/messages/messageListener.ts
index d9214f43365..93c750ab01b 100644
--- a/packages/core/src/codewhispererChat/view/messages/messageListener.ts
+++ b/packages/core/src/codewhispererChat/view/messages/messageListener.ts
@@ -63,6 +63,12 @@ export class UIMessageListener {
})
}
break
+ case 'accept_diff':
+ this.processAcceptDiff(msg)
+ break
+ case 'view_diff':
+ this.processViewDiff(msg)
+ break
case 'code_was_copied_to_clipboard':
this.processCodeWasCopiedToClipboard(msg)
break
@@ -159,6 +165,23 @@ export class UIMessageListener {
eventId: msg.eventId,
codeBlockIndex: msg.codeBlockIndex,
totalCodeBlocks: msg.totalCodeBlocks,
+ codeBlockLanguage: msg.codeBlockLanguage,
+ })
+ }
+
+ private processAcceptDiff(msg: any) {
+ this.chatControllerMessagePublishers.processAcceptDiff.publish({
+ command: msg.command,
+ tabID: msg.tabID || msg.tabId,
+ ...msg,
+ })
+ }
+
+ private processViewDiff(msg: any) {
+ this.chatControllerMessagePublishers.processViewDiff.publish({
+ command: msg.command,
+ tabID: msg.tabID || msg.tabId,
+ ...msg,
})
}
@@ -174,6 +197,7 @@ export class UIMessageListener {
eventId: msg.eventId,
codeBlockIndex: msg.codeBlockIndex,
totalCodeBlocks: msg.totalCodeBlocks,
+ codeBlockLanguage: msg.codeBlockLanguage,
})
}
diff --git a/packages/core/src/dev/activation.ts b/packages/core/src/dev/activation.ts
index df69c018791..e676c426b12 100644
--- a/packages/core/src/dev/activation.ts
+++ b/packages/core/src/dev/activation.ts
@@ -4,7 +4,6 @@
*/
import * as vscode from 'vscode'
-import * as config from './config'
import { createCommonButtons } from '../shared/ui/buttons'
import { createQuickPick } from '../shared/ui/pickerPrompter'
import { SkipPrompter } from '../shared/ui/common/skipPrompter'
@@ -14,9 +13,6 @@ import { Commands } from '../shared/vscode/commands2'
import { createInputBox } from '../shared/ui/inputPrompter'
import { Wizard } from '../shared/wizards/wizard'
import { deleteDevEnvCommand, installVsixCommand, openTerminalCommand } from './codecatalyst'
-import { watchBetaVSIX } from './beta'
-import { isCloud9 } from '../shared/extensionUtilities'
-import { isReleaseVersion } from '../shared/vscode/env'
import { isAnySsoConnection } from '../auth/connection'
import { Auth } from '../auth/auth'
import { getLogger } from '../shared/logger'
@@ -61,57 +57,59 @@ let targetAuth: Auth
* on selection. There is no support for name-spacing. Just add the relevant
* feature/module as a description so it can be moved around easier.
*/
-const menuOptions: Record = {
- installVsix: {
- label: 'Install VSIX on Remote Environment',
- description: 'CodeCatalyst',
- detail: 'Automatically upload/install a VSIX to a remote host',
- executor: installVsixCommand,
- },
- openTerminal: {
- label: 'Open Remote Terminal',
- description: 'CodeCatalyst',
- detail: 'Opens a new terminal connected to the remote environment',
- executor: openTerminalCommand,
- },
- deleteDevEnv: {
- label: 'Delete Workspace',
- description: 'CodeCatalyst',
- detail: 'Deletes the selected Dev Environment',
- executor: deleteDevEnvCommand,
- },
- editStorage: {
- label: 'Show or Edit globalState',
- description: 'VS Code',
- detail: 'Shows all globalState values, or edit a globalState/secret item',
- executor: openStorageFromInput,
- },
- showEnvVars: {
- label: 'Show Environment Variables',
- description: 'AWS Toolkit',
- detail: 'Shows all environment variable values',
- executor: () => showState('envvars'),
- },
- deleteSsoConnections: {
- label: 'Auth: Delete SSO Connections',
- detail: 'Deletes all SSO Connections the extension is using.',
- executor: deleteSsoConnections,
- },
- expireSsoConnections: {
- label: 'Auth: Expire SSO Connections',
- detail: 'Force expires all SSO Connections, in to a "needs reauthentication" state.',
- executor: expireSsoConnections,
- },
- editAuthConnections: {
- label: 'Auth: Edit Connections',
- detail: 'Opens editor to all Auth Connections the extension is using.',
- executor: editSsoConnections,
- },
- forceIdeCrash: {
- label: 'Crash: Force IDE ExtHost Crash',
- detail: `Will SIGKILL ExtHost, { pid: ${process.pid}, sessionId: '${getSessionId().slice(0, 8)}-...' }, but the IDE itself will not crash.`,
- executor: forceQuitIde,
- },
+const menuOptions: () => Record = () => {
+ return {
+ installVsix: {
+ label: 'Install VSIX on Remote Environment',
+ description: 'CodeCatalyst',
+ detail: 'Automatically upload/install a VSIX to a remote host',
+ executor: installVsixCommand,
+ },
+ openTerminal: {
+ label: 'Open Remote Terminal',
+ description: 'CodeCatalyst',
+ detail: 'Opens a new terminal connected to the remote environment',
+ executor: openTerminalCommand,
+ },
+ deleteDevEnv: {
+ label: 'Delete Workspace',
+ description: 'CodeCatalyst',
+ detail: 'Deletes the selected Dev Environment',
+ executor: deleteDevEnvCommand,
+ },
+ editStorage: {
+ label: 'Show or Edit globalState',
+ description: 'VS Code',
+ detail: 'Shows all globalState values, or edit a globalState/secret item',
+ executor: openStorageFromInput,
+ },
+ showEnvVars: {
+ label: 'Show Environment Variables',
+ description: 'AWS Toolkit',
+ detail: 'Shows all environment variable values',
+ executor: () => showState('envvars'),
+ },
+ deleteSsoConnections: {
+ label: 'Auth: Delete SSO Connections',
+ detail: 'Deletes all SSO Connections the extension is using.',
+ executor: deleteSsoConnections,
+ },
+ expireSsoConnections: {
+ label: 'Auth: Expire SSO Connections',
+ detail: 'Force expires all SSO Connections, in to a "needs reauthentication" state.',
+ executor: expireSsoConnections,
+ },
+ editAuthConnections: {
+ label: 'Auth: Edit Connections',
+ detail: 'Opens editor to all Auth Connections the extension is using.',
+ executor: editSsoConnections,
+ },
+ forceIdeCrash: {
+ label: 'Crash: Force IDE ExtHost Crash',
+ detail: `Will SIGKILL ExtHost, { pid: ${process.pid}, sessionId: '${getSessionId().slice(0, 8)}-...' }, but the IDE itself will not crash.`,
+ executor: forceQuitIde,
+ },
+ }
}
/**
@@ -167,7 +165,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise {
globalState = targetContext.globalState
targetAuth = opts.auth
void openMenu(
- entries(menuOptions)
+ entries(menuOptions())
.filter((e) => (opts.menuOptions ?? Object.keys(menuOptions)).includes(e[0]))
.map((e) => e[1])
)
@@ -192,10 +190,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise {
const editor = new ObjectEditor()
ctx.subscriptions.push(openStorageCommand.register(editor))
-
- if (!isCloud9() && !isReleaseVersion() && config.betaUrl) {
- ctx.subscriptions.push(watchBetaVSIX(config.betaUrl))
- }
}
async function openMenu(options: MenuOption[]): Promise {
diff --git a/packages/core/src/dev/beta.ts b/packages/core/src/dev/beta.ts
index 69935ace908..7718d1f49fc 100644
--- a/packages/core/src/dev/beta.ts
+++ b/packages/core/src/dev/beta.ts
@@ -18,6 +18,9 @@ import { isUserCancelledError, ToolkitError } from '../shared/errors'
import { telemetry } from '../shared/telemetry/telemetry'
import { cast } from '../shared/utilities/typeConstructors'
import { CancellationError } from '../shared/utilities/timeoutUtils'
+import { isAmazonQ, isCloud9, productName } from '../shared/extensionUtilities'
+import * as config from './config'
+import { isReleaseVersion } from '../shared/vscode/env'
const localize = nls.loadMessageBundle()
@@ -39,6 +42,16 @@ async function updateBetaToolkitData(vsixUrl: string, data: BetaToolkit) {
})
}
+/**
+ * Set up "beta" update monitoring.
+ */
+export async function activate(ctx: vscode.ExtensionContext) {
+ const betaUrl = isAmazonQ() ? config.betaUrl.amazonq : config.betaUrl.toolkit
+ if (!isCloud9() && !isReleaseVersion() && betaUrl) {
+ ctx.subscriptions.push(watchBetaVSIX(betaUrl))
+ }
+}
+
/**
* Watch the beta VSIX daily for changes.
* If this is the first time we are watching the beta version or if its been 24 hours since it was last checked then try to prompt for update
@@ -75,11 +88,12 @@ async function runAutoUpdate(vsixUrl: string) {
async function checkBetaUrl(vsixUrl: string): Promise {
const resp = await got(vsixUrl).buffer()
const latestBetaInfo = await getExtensionInfo(resp)
- if (VSCODE_EXTENSION_ID.awstoolkit !== `${latestBetaInfo.publisher}.${latestBetaInfo.name}`) {
+ const extId = isAmazonQ() ? VSCODE_EXTENSION_ID.amazonq : VSCODE_EXTENSION_ID.awstoolkit
+ if (extId !== `${latestBetaInfo.publisher}.${latestBetaInfo.name}`) {
throw new ToolkitError('URL does not point to an AWS Toolkit artifact', { code: 'InvalidExtensionName' })
}
- const currentVersion = vscode.extensions.getExtension(VSCODE_EXTENSION_ID.awstoolkit)?.packageJSON.version
+ const currentVersion = vscode.extensions.getExtension(extId)?.packageJSON.version
if (latestBetaInfo.version !== currentVersion) {
const tmpFolder = await makeTemporaryToolkitFolder()
const betaPath = vscode.Uri.joinPath(vscode.Uri.file(tmpFolder), path.basename(vsixUrl))
@@ -141,7 +155,7 @@ async function promptInstallToolkit(pluginPath: vscode.Uri, newVersion: string,
const response = await vscode.window.showInformationMessage(
localize(
'AWS.dev.beta.updatePrompt',
- `New version of AWS Toolkit is available at the [beta URL]({0}). Install the new version "{1}" to continue using the beta.`,
+ `New version of ${productName()} is available at the [beta URL]({0}). Install the new version "{1}" to continue using the beta.`,
vsixUrl,
newVersion
),
diff --git a/packages/core/src/dev/config.ts b/packages/core/src/dev/config.ts
index 219f9b35fec..276732a72bf 100644
--- a/packages/core/src/dev/config.ts
+++ b/packages/core/src/dev/config.ts
@@ -3,7 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
-// This file is strictly used for private development
+// This file is for internal testing.
// Nothing in this file should have a truthy value on mainline
-export const betaUrl = ''
+export const betaUrl = {
+ amazonq: '',
+ toolkit: '',
+}
+
+// feature flag for SQL transformations
+export const isSQLTransformReady = false
diff --git a/packages/core/src/dev/index.ts b/packages/core/src/dev/index.ts
index e69b1908875..111920c99ee 100644
--- a/packages/core/src/dev/index.ts
+++ b/packages/core/src/dev/index.ts
@@ -4,3 +4,4 @@
*/
export { DevOptions, updateDevMode } from './activation'
+export * as beta from './beta'
diff --git a/packages/core/src/dynamicResources/awsResourceManager.ts b/packages/core/src/dynamicResources/awsResourceManager.ts
index 6c09423be64..a6045559675 100644
--- a/packages/core/src/dynamicResources/awsResourceManager.ts
+++ b/packages/core/src/dynamicResources/awsResourceManager.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { writeFileSync } from 'fs'
+import { writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports
import * as path from 'path'
import * as vscode from 'vscode'
import { CloudFormationClient } from '../shared/clients/cloudFormationClient'
diff --git a/packages/core/src/extensionNode.ts b/packages/core/src/extensionNode.ts
index f91fa321f59..b9ac4977b22 100644
--- a/packages/core/src/extensionNode.ts
+++ b/packages/core/src/extensionNode.ts
@@ -7,10 +7,9 @@ import * as vscode from 'vscode'
import * as nls from 'vscode-nls'
import * as codecatalyst from './codecatalyst/activation'
+import { activate as activateAppBuilder } from './awsService/appBuilder/activation'
import { activate as activateAwsExplorer } from './awsexplorer/activation'
import { activate as activateCloudWatchLogs } from './awsService/cloudWatchLogs/activation'
-import { CredentialsProviderManager } from './auth/providers/credentialsProviderManager'
-import { SharedCredentialsProviderFactory } from './auth/providers/sharedCredentialsProviderFactory'
import { activate as activateSchemas } from './eventSchemas/activation'
import { activate as activateLambda } from './lambda/activation'
import { activate as activateCloudFormationTemplateRegistry } from './shared/cloudformation/activation'
@@ -24,7 +23,7 @@ import {
} from './shared/extensionUtilities'
import { getLogger, Logger } from './shared/logger/logger'
import { activate as activateEcr } from './awsService/ecr/activation'
-import { activate as activateEc2 } from './awsService/ec2/activation'
+import { activate as activateEc2, deactivate as deactivateEc2 } from './awsService/ec2/activation'
import { activate as activateSam } from './shared/sam/activation'
import { activate as activateS3 } from './awsService/s3/activation'
import * as filetypes from './shared/filetypes'
@@ -36,12 +35,10 @@ import { activate as activateEcs } from './awsService/ecs/activation'
import { activate as activateAppRunner } from './awsService/apprunner/activation'
import { activate as activateIot } from './awsService/iot/activation'
import { activate as activateDev } from './dev/activation'
+import * as beta from './dev/beta'
import { activate as activateApplicationComposer } from './applicationcomposer/activation'
import { activate as activateRedshift } from './awsService/redshift/activation'
import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation'
-import { Ec2CredentialsProvider } from './auth/providers/ec2CredentialsProvider'
-import { EnvVarsCredentialsProvider } from './auth/providers/envVarsCredentialsProvider'
-import { EcsCredentialsProvider } from './auth/providers/ecsCredentialsProvider'
import { SchemaService } from './shared/schemas'
import { AwsResourceManager } from './dynamicResources/awsResourceManager'
import globals from './shared/extensionGlobals'
@@ -55,7 +52,7 @@ import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './
import { AuthUtil, codeWhispererCoreScopes, isPreviousQUser } from './codewhisperer/util/authUtil'
import { installAmazonQExtension } from './codewhisperer/commands/basicCommands'
import { isExtensionInstalled, VSCODE_EXTENSION_ID } from './shared/utilities'
-import { ExtensionUse } from './auth/utils'
+import { ExtensionUse, initializeCredentialsProviderManager } from './auth/utils'
import { ExtStartUpSources } from './shared/telemetry'
import { activate as activateThreatComposerEditor } from './threatComposer/activation'
import { isSsoConnection, hasScopes } from './auth/connection'
@@ -78,7 +75,8 @@ export async function activate(context: vscode.ExtensionContext) {
// IMPORTANT: If you are doing setup that should also work in web mode (browser), it should be done in the function below
const extContext = await activateCommon(context, contextPrefix, false)
- await (await CrashMonitoring.instance()).start()
+ // Intentionally do not await since this can be slow and non-critical
+ void (await CrashMonitoring.instance())?.start()
initializeCredentialsProviderManager()
@@ -109,6 +107,7 @@ export async function activate(context: vscode.ExtensionContext) {
try {
await activateDev(context)
+ await beta.activate(context)
} catch (error) {
getLogger().debug(`Developer Tools (internal): failed to activate: ${(error as Error).message}`)
}
@@ -207,6 +206,8 @@ export async function activate(context: vscode.ExtensionContext) {
await activateRedshift(extContext)
+ await activateAppBuilder(extContext)
+
await activateIamPolicyChecks(extContext)
context.subscriptions.push(
@@ -254,7 +255,7 @@ export async function activate(context: vscode.ExtensionContext) {
export async function deactivate() {
// Run concurrently to speed up execution. stop() does not throw so it is safe
- await Promise.all([await (await CrashMonitoring.instance()).stop(), deactivateCommon()])
+ await Promise.all([await (await CrashMonitoring.instance())?.shutdown(), deactivateCommon(), deactivateEc2()])
await globals.resourceManager.dispose()
}
@@ -309,12 +310,6 @@ async function handleAmazonQInstall() {
})
}
-function initializeCredentialsProviderManager() {
- const manager = CredentialsProviderManager.getInstance()
- manager.addProviderFactory(new SharedCredentialsProviderFactory())
- manager.addProviders(new Ec2CredentialsProvider(), new EcsCredentialsProvider(), new EnvVarsCredentialsProvider())
-}
-
function recordToolkitInitialization(activationStartedOn: number, settingsValid: boolean, logger?: Logger) {
try {
const activationFinishedOn = Date.now()
diff --git a/packages/core/src/lambda/activation.ts b/packages/core/src/lambda/activation.ts
index 911f3f9be44..4a21b2e9611 100644
--- a/packages/core/src/lambda/activation.ts
+++ b/packages/core/src/lambda/activation.ts
@@ -11,25 +11,36 @@ import { downloadLambdaCommand } from './commands/downloadLambda'
import { tryRemoveFolder } from '../shared/filesystemUtilities'
import { ExtContext } from '../shared/extensions'
import { invokeRemoteLambda } from './vue/remoteInvoke/invokeLambda'
-import { registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend'
+import { registerSamDebugInvokeVueCommand, registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend'
import { Commands } from '../shared/vscode/commands2'
import { DefaultLambdaClient } from '../shared/clients/lambdaClient'
import { copyLambdaUrl } from './commands/copyLambdaUrl'
+import { ResourceNode } from '../awsService/appBuilder/explorer/nodes/resourceNode'
+import { isTreeNode, TreeNode } from '../shared/treeview/resourceTreeDataProvider'
+import { getSourceNode } from '../shared/utilities/treeNodeUtils'
/**
* Activates Lambda components.
*/
export async function activate(context: ExtContext): Promise {
context.extensionContext.subscriptions.push(
- Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode) => {
- await deleteLambda(node.configuration, new DefaultLambdaClient(node.regionCode))
- await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node.parent)
+ Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode | TreeNode) => {
+ const sourceNode = getSourceNode(node)
+ await deleteLambda(sourceNode.configuration, new DefaultLambdaClient(sourceNode.regionCode))
+ await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', sourceNode.parent)
+ }),
+ Commands.register('aws.invokeLambda', async (node: LambdaFunctionNode | TreeNode) => {
+ let source: string = 'AwsExplorerRemoteInvoke'
+ if (isTreeNode(node)) {
+ node = getSourceNode(node)
+ source = 'AppBuilderRemoteInvoke'
+ }
+ await invokeRemoteLambda(context, {
+ outputChannel: context.outputChannel,
+ functionNode: node,
+ source: source,
+ })
}),
- Commands.register(
- 'aws.invokeLambda',
- async (node: LambdaFunctionNode) =>
- await invokeRemoteLambda(context, { outputChannel: context.outputChannel, functionNode: node })
- ),
// Capture debug finished events, and delete the temporary directory if it exists
vscode.debug.onDidTerminateDebugSession(async (session) => {
if (
@@ -39,7 +50,10 @@ export async function activate(context: ExtContext): Promise {
await tryRemoveFolder(session.configuration.baseBuildDir)
}
}),
- Commands.register('aws.downloadLambda', async (node: LambdaFunctionNode) => await downloadLambdaCommand(node)),
+ Commands.register('aws.downloadLambda', async (node: LambdaFunctionNode | TreeNode) => {
+ const sourceNode = getSourceNode(node)
+ await downloadLambdaCommand(sourceNode)
+ }),
Commands.register({ id: 'aws.uploadLambda', autoconnect: true }, async (arg?: unknown) => {
if (arg instanceof LambdaFunctionNode) {
await uploadLambdaCommand({
@@ -53,10 +67,15 @@ export async function activate(context: ExtContext): Promise {
await uploadLambdaCommand()
}
}),
- Commands.register(
- 'aws.copyLambdaUrl',
- async (node: LambdaFunctionNode) => await copyLambdaUrl(node, new DefaultLambdaClient(node.regionCode))
- ),
- registerSamInvokeVueCommand(context)
+ Commands.register('aws.copyLambdaUrl', async (node: LambdaFunctionNode | TreeNode) => {
+ const sourceNode = getSourceNode(node)
+ await copyLambdaUrl(sourceNode, new DefaultLambdaClient(sourceNode.regionCode))
+ }),
+
+ registerSamInvokeVueCommand(context),
+
+ Commands.register('aws.launchDebugConfigForm', async (node: ResourceNode) =>
+ registerSamDebugInvokeVueCommand(context, { resource: node })
+ )
)
}
diff --git a/packages/core/src/lambda/commands/deploySamApplication.ts b/packages/core/src/lambda/commands/deploySamApplication.ts
deleted file mode 100644
index 4231b2b614e..00000000000
--- a/packages/core/src/lambda/commands/deploySamApplication.ts
+++ /dev/null
@@ -1,326 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-
-import { asEnvironmentVariables } from '../../auth/credentials/utils'
-import { AwsContext } from '../../shared/awsContext'
-import globals from '../../shared/extensionGlobals'
-
-import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
-import { checklogs } from '../../shared/localizedText'
-import { getLogger } from '../../shared/logger'
-import { SamCliBuildInvocation } from '../../shared/sam/cli/samCliBuild'
-import { SamCliSettings } from '../../shared/sam/cli/samCliSettings'
-import { getSamCliContext, SamCliContext, getSamCliVersion } from '../../shared/sam/cli/samCliContext'
-import { runSamCliDeploy } from '../../shared/sam/cli/samCliDeploy'
-import { SamCliProcessInvoker } from '../../shared/sam/cli/samCliInvokerUtils'
-import { runSamCliPackage } from '../../shared/sam/cli/samCliPackage'
-import { throwAndNotifyIfInvalid } from '../../shared/sam/cli/samCliValidationUtils'
-import { Result } from '../../shared/telemetry/telemetry'
-import { addCodiconToString } from '../../shared/utilities/textUtilities'
-import { SamDeployWizardResponse } from '../wizards/samDeployWizard'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-const localize = nls.loadMessageBundle()
-
-interface DeploySamApplicationParameters {
- sourceTemplatePath: string
- deployRootFolder: string
- environmentVariables: NodeJS.ProcessEnv
- region: string
- packageBucketName: string
- ecrRepo?: string
- destinationStackName: string
- parameterOverrides: Map
-}
-
-export interface WindowFunctions {
- showInformationMessage: typeof vscode.window.showInformationMessage
- showErrorMessage: typeof vscode.window.showErrorMessage
- setStatusBarMessage(text: string, hideWhenDone: Thenable): vscode.Disposable
-}
-
-export async function deploySamApplication(
- {
- samCliContext = getSamCliContext(),
- samDeployWizard,
- }: {
- samCliContext?: SamCliContext
- samDeployWizard: () => Promise
- },
- {
- awsContext,
- settings,
- window = getDefaultWindowFunctions(),
- refreshFn = () => {
- // no need to await, doesn't need to block further execution (true -> no telemetry)
- void vscode.commands.executeCommand('aws.refreshAwsExplorer', true)
- },
- }: {
- awsContext: Pick
- settings: SamCliSettings
- window?: WindowFunctions
- refreshFn?: () => void
- }
-): Promise {
- let deployResult: Result = 'Succeeded'
- let samVersion: string | undefined
- let deployFolder: string | undefined
- try {
- const credentials = await awsContext.getCredentials()
- if (!credentials) {
- throw new Error('No AWS profile selected')
- }
-
- throwAndNotifyIfInvalid(await samCliContext.validator.detectValidSamCli())
-
- const deployWizardResponse = await samDeployWizard()
-
- if (!deployWizardResponse) {
- return
- }
-
- deployFolder = await makeTemporaryToolkitFolder('samDeploy')
- samVersion = await getSamCliVersion(samCliContext)
-
- const deployParameters: DeploySamApplicationParameters = {
- deployRootFolder: deployFolder,
- destinationStackName: deployWizardResponse.stackName,
- packageBucketName: deployWizardResponse.s3Bucket,
- ecrRepo: deployWizardResponse.ecrRepo?.repositoryUri,
- parameterOverrides: deployWizardResponse.parameterOverrides,
- environmentVariables: asEnvironmentVariables(credentials),
- region: deployWizardResponse.region,
- sourceTemplatePath: deployWizardResponse.template.fsPath,
- }
-
- const deployApplicationPromise = deploy({
- deployParameters,
- invoker: samCliContext.invoker,
- window,
- })
-
- window.setStatusBarMessage(
- addCodiconToString(
- 'cloud-upload',
- localize(
- 'AWS.samcli.deploy.statusbar.message',
- 'Deploying SAM Application to {0}...',
- deployWizardResponse.stackName
- )
- ),
- deployApplicationPromise
- )
-
- await deployApplicationPromise
- refreshFn()
-
- // successful deploy: retain S3 bucket for quick future access
- const profile = awsContext.getCredentialProfileName()
- if (profile) {
- await settings.updateSavedBuckets(profile, deployWizardResponse.region, deployWizardResponse.s3Bucket)
- } else {
- getLogger().warn('Profile not provided; cannot write recent buckets.')
- }
- } catch (err) {
- deployResult = 'Failed'
- outputDeployError(err as Error)
- void vscode.window.showErrorMessage(
- localize('AWS.samcli.deploy.workflow.error', 'Failed to deploy SAM application.')
- )
- } finally {
- await tryRemoveFolder(deployFolder)
- telemetry.sam_deploy.emit({ result: deployResult, version: samVersion })
- }
-}
-
-function getBuildRootFolder(deployRootFolder: string): string {
- return path.join(deployRootFolder, 'build')
-}
-
-function getBuildTemplatePath(deployRootFolder: string): string {
- // Assumption: sam build will always produce a template.yaml file.
- // If that is not the case, revisit this logic.
- return path.join(getBuildRootFolder(deployRootFolder), 'template.yaml')
-}
-
-function getPackageTemplatePath(deployRootFolder: string): string {
- return path.join(deployRootFolder, 'template.yaml')
-}
-
-async function buildOperation(params: {
- deployParameters: DeploySamApplicationParameters
- invoker: SamCliProcessInvoker
-}): Promise {
- try {
- getLogger().info(localize('AWS.samcli.deploy.workflow.init', 'Building SAM Application...'))
-
- const buildDestination = getBuildRootFolder(params.deployParameters.deployRootFolder)
-
- const build = new SamCliBuildInvocation({
- buildDir: buildDestination,
- baseDir: undefined,
- templatePath: params.deployParameters.sourceTemplatePath,
- invoker: params.invoker,
- })
-
- await build.execute()
-
- return true
- } catch (err) {
- getLogger().warn(
- localize(
- 'AWS.samcli.build.failedBuild',
- '"sam build" failed: {0}',
- params.deployParameters.sourceTemplatePath
- )
- )
- return false
- }
-}
-
-async function packageOperation(
- params: {
- deployParameters: DeploySamApplicationParameters
- invoker: SamCliProcessInvoker
- },
- buildSuccessful: boolean
-): Promise {
- if (!buildSuccessful) {
- void vscode.window.showInformationMessage(
- localize(
- 'AWS.samcli.deploy.workflow.packaging.noBuild',
- 'Attempting to package source template directory directly since "sam build" failed'
- )
- )
- }
-
- getLogger().info(
- localize(
- 'AWS.samcli.deploy.workflow.packaging',
- 'Packaging SAM Application to S3 Bucket: {0}',
- params.deployParameters.packageBucketName
- )
- )
-
- // HACK: Attempt to package the initial template if the build fails.
- const buildTemplatePath = buildSuccessful
- ? getBuildTemplatePath(params.deployParameters.deployRootFolder)
- : params.deployParameters.sourceTemplatePath
- const packageTemplatePath = getPackageTemplatePath(params.deployParameters.deployRootFolder)
-
- await runSamCliPackage(
- {
- sourceTemplateFile: buildTemplatePath,
- destinationTemplateFile: packageTemplatePath,
- environmentVariables: params.deployParameters.environmentVariables,
- region: params.deployParameters.region,
- s3Bucket: params.deployParameters.packageBucketName,
- ecrRepo: params.deployParameters.ecrRepo,
- },
- params.invoker
- )
-}
-
-async function deployOperation(params: {
- deployParameters: DeploySamApplicationParameters
- invoker: SamCliProcessInvoker
-}): Promise {
- try {
- getLogger().info(
- localize(
- 'AWS.samcli.deploy.workflow.stackName.initiated',
- 'Deploying SAM Application to CloudFormation Stack: {0}',
- params.deployParameters.destinationStackName
- )
- )
-
- const packageTemplatePath = getPackageTemplatePath(params.deployParameters.deployRootFolder)
-
- await runSamCliDeploy(
- {
- parameterOverrides: params.deployParameters.parameterOverrides,
- environmentVariables: params.deployParameters.environmentVariables,
- templateFile: packageTemplatePath,
- region: params.deployParameters.region,
- stackName: params.deployParameters.destinationStackName,
- s3Bucket: params.deployParameters.packageBucketName,
- ecrRepo: params.deployParameters.ecrRepo,
- },
- params.invoker
- )
- } catch (err) {
- // Handle sam deploy Errors to supplement the error message prior to writing it out
- const error = err as Error
-
- getLogger().error(error)
-
- const errorMessage = enhanceAwsCloudFormationInstructions(String(err), params.deployParameters)
- globals.outputChannel.appendLine(errorMessage)
-
- throw new Error('Deploy failed')
- }
-}
-
-async function deploy(params: {
- deployParameters: DeploySamApplicationParameters
- invoker: SamCliProcessInvoker
- window: WindowFunctions
-}): Promise {
- globals.outputChannel.show(true)
- getLogger().info(localize('AWS.samcli.deploy.workflow.start', 'Starting SAM Application deployment...'))
-
- const buildSuccessful = await buildOperation(params)
- await packageOperation(params, buildSuccessful)
- await deployOperation(params)
-
- getLogger().info(
- localize(
- 'AWS.samcli.deploy.workflow.success',
- 'Deployed SAM Application to CloudFormation Stack: {0}',
- params.deployParameters.destinationStackName
- )
- )
-
- void params.window.showInformationMessage(
- localize('AWS.samcli.deploy.workflow.success.general', 'SAM Application deployment succeeded.')
- )
-}
-
-function enhanceAwsCloudFormationInstructions(
- message: string,
- deployParameters: DeploySamApplicationParameters
-): string {
- // detect error message from https://github.com/aws/aws-cli/blob/4ff0cbacbac69a21d4dd701921fe0759cf7852ed/awscli/customizations/cloudformation/exceptions.py#L42
- // and append region to assist in troubleshooting the error
- // (command uses CLI configured value--users that don't know this and omit region won't see error)
- if (
- message.includes(
- `aws cloudformation describe-stack-events --stack-name ${deployParameters.destinationStackName}`
- )
- ) {
- message += ` --region ${deployParameters.region}`
- }
-
- return message
-}
-
-function outputDeployError(error: Error) {
- getLogger().error(error)
-
- globals.outputChannel.show(true)
- getLogger().error('AWS.samcli.deploy.general.error', 'Error deploying a SAM Application. {0}', checklogs())
-}
-
-function getDefaultWindowFunctions(): WindowFunctions {
- return {
- setStatusBarMessage: vscode.window.setStatusBarMessage,
- showErrorMessage: vscode.window.showErrorMessage,
- showInformationMessage: vscode.window.showInformationMessage,
- }
-}
diff --git a/packages/core/src/lambda/commands/listSamResources.ts b/packages/core/src/lambda/commands/listSamResources.ts
new file mode 100644
index 00000000000..5a0d1678c9b
--- /dev/null
+++ b/packages/core/src/lambda/commands/listSamResources.ts
@@ -0,0 +1,43 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as vscode from 'vscode'
+import { getLogger } from '../../shared/logger'
+import { runSamCliListResource } from '../../shared/sam/cli/samCliListResources'
+
+export interface StackResource {
+ LogicalResourceId: string
+ PhysicalResourceId: string
+}
+
+/*
+This function return exclusively the deployed resources
+Newly added but yet-to-be deployed resources are not included in this result
+*/
+export async function getDeployedResources(params: any) {
+ try {
+ const samCliListResourceOutput = await runSamCliListResource(params.listResourcesParams, params.invoker).then(
+ (output) => parseSamListResourceOutput(output)
+ )
+ // Filter out resources that are not deployed
+ return samCliListResourceOutput.filter((resource) => resource.PhysicalResourceId !== '-')
+ } catch (err) {
+ const error = err as Error
+ getLogger().error(error)
+ }
+}
+
+function parseSamListResourceOutput(output: any): StackResource[] {
+ try {
+ if ((Array.isArray(output) && output.length === 0) || '[]' === output) {
+ // Handle if the output is instance or stringify version of an empty array to avoid parsing error
+ return []
+ }
+ return JSON.parse(output) as StackResource[]
+ } catch (error: any) {
+ void vscode.window.showErrorMessage(`Failed to parse SAM CLI output: ${error.message}`)
+ return []
+ }
+}
diff --git a/packages/core/src/lambda/models/samLambdaRuntime.ts b/packages/core/src/lambda/models/samLambdaRuntime.ts
index 42a8681c672..5b97ef06e2a 100644
--- a/packages/core/src/lambda/models/samLambdaRuntime.ts
+++ b/packages/core/src/lambda/models/samLambdaRuntime.ts
@@ -22,6 +22,7 @@ export enum RuntimeFamily {
DotNet,
Go,
Java,
+ Ruby,
}
export type RuntimePackageType = 'Image' | 'Zip'
@@ -57,8 +58,15 @@ export const pythonRuntimes: ImmutableSet = ImmutableSet([
'python3.7',
])
export const goRuntimes: ImmutableSet = ImmutableSet(['go1.x'])
-export const javaRuntimes: ImmutableSet = ImmutableSet(['java17', 'java11', 'java8', 'java8.al2'])
-export const dotNetRuntimes: ImmutableSet = ImmutableSet(['dotnet6'])
+export const javaRuntimes: ImmutableSet = ImmutableSet([
+ 'java17',
+ 'java11',
+ 'java8',
+ 'java8.al2',
+ 'java21',
+])
+export const dotNetRuntimes: ImmutableSet = ImmutableSet(['dotnet6', 'dotnet8'])
+export const rubyRuntimes: ImmutableSet = ImmutableSet(['ruby3.2', 'ruby3.3'])
/**
* Deprecated runtimes can be found at https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
@@ -77,6 +85,8 @@ export const deprecatedRuntimes: ImmutableSet = ImmutableSet([
'nodejs8.10',
'nodejs10.x',
'nodejs12.x',
+ 'ruby2.5',
+ 'ruby2.7',
])
const defaultRuntimes = ImmutableMap([
[RuntimeFamily.NodeJS, 'nodejs20.x'],
@@ -84,6 +94,7 @@ const defaultRuntimes = ImmutableMap([
[RuntimeFamily.DotNet, 'dotnet6'],
[RuntimeFamily.Go, 'go1.x'],
[RuntimeFamily.Java, 'java17'],
+ [RuntimeFamily.Ruby, 'ruby3.3'],
])
export const samZipLambdaRuntimes: ImmutableSet = ImmutableSet.union([
@@ -157,6 +168,8 @@ export function getFamily(runtime: string): RuntimeFamily {
return RuntimeFamily.Go
} else if (javaRuntimes.has(runtime)) {
return RuntimeFamily.Java
+ } else if (rubyRuntimes.has(runtime)) {
+ return RuntimeFamily.Ruby
}
return RuntimeFamily.Unknown
}
@@ -206,6 +219,10 @@ export function getRuntimeFamily(langId: string): RuntimeFamily {
return RuntimeFamily.Python
case 'go':
return RuntimeFamily.Go
+ case 'java':
+ return RuntimeFamily.Java
+ case 'ruby':
+ return RuntimeFamily.Ruby
default:
return RuntimeFamily.Unknown
}
@@ -258,7 +275,7 @@ export function createRuntimeQuickPick(params: {
totalSteps?: number
}): QuickPickPrompter {
const zipRuntimes = params.runtimeFamily
- ? getRuntimesForFamily(params.runtimeFamily) ?? samLambdaCreatableRuntimes()
+ ? (getRuntimesForFamily(params.runtimeFamily) ?? samLambdaCreatableRuntimes())
: samLambdaCreatableRuntimes()
const zipRuntimeItems = zipRuntimes
diff --git a/packages/core/src/lambda/vue/configEditor/samInvoke.css b/packages/core/src/lambda/vue/configEditor/samInvoke.css
index d248e071a90..9ca2c8ef452 100644
--- a/packages/core/src/lambda/vue/configEditor/samInvoke.css
+++ b/packages/core/src/lambda/vue/configEditor/samInvoke.css
@@ -1,7 +1,3 @@
-form {
- padding: 15px;
-}
-
.section-header {
margin: 0px;
margin-bottom: 10px;
@@ -10,7 +6,9 @@ form {
}
textarea {
- max-width: 100%;
+ color: var(--vscode-settings-textInputForeground);
+ background: var(--vscode-settings-textInputBackground);
+ border: 1px solid var(--vscode-settings-textInputBorder);
}
.config-item {
@@ -47,7 +45,133 @@ textarea {
margin-bottom: 16px;
}
+.header-buttons {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
#target-type-selector {
margin-bottom: 15px;
margin-left: 8px;
}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 150px 1fr;
+ margin-bottom: 10px;
+}
+
+.form-control {
+ min-width: 170%; /* Set a minimum width */
+ width: 100%; /* Allow the width to adjust based on content */
+ display: inline-block;
+ flex-grow: 1;
+ margin-right: 0.5rem;
+}
+
+.payload-options-button {
+ display: grid;
+ align-items: center;
+ border: none;
+ padding: 5px 10px;
+ cursor: pointer;
+ font-size: 0.9em;
+ margin-bottom: 10px;
+}
+
+.payload-options-buttons {
+ display: flex;
+ align-items: center;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.Icontainer {
+ margin-inline: auto;
+ margin-top: 5rem;
+}
+
+.container {
+ width: 574px;
+ height: 824px;
+ top: 18px;
+ gap: 20px;
+ margin: auto;
+ left: 688px;
+ background-color: var(--vscode-editor-background);
+}
+
+.container em {
+ display: block;
+ text-align: justify;
+}
+
+.button-theme-primary {
+ color: var(--vscode-button-foreground);
+ background: var(--vscode-button-background);
+ border: 1px solid var(--vscode-button-border);
+ padding: 8px 12px;
+}
+.button-theme-primary:hover:not(:disabled) {
+ background: var(--vscode-button-hoverBackground);
+ cursor: pointer;
+}
+.button-theme-secondary {
+ color: var(--vscode-button-secondaryForeground);
+ background: var(--vscode-button-secondaryBackground);
+ border: 1px solid var(--vscode-button-border);
+ padding: 8px 12px;
+}
+.button-theme-secondary:hover:not(:disabled) {
+ background: var(--vscode-button-secondaryHoverBackground);
+ cursor: pointer;
+}
+
+.formfield {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.5rem;
+}
+
+.payload-options-buttons {
+ display: flex;
+ align-items: center;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.radio-selector {
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+}
+
+.label-selector {
+ padding-left: 7px;
+ font-weight: 500;
+ font-size: 13px;
+ line-height: 15.51px;
+ text-align: center;
+}
+
+.form-row-select {
+ width: 387px;
+ height: 28px;
+ border: 1px;
+ border-radius: 5px;
+ gap: 4px;
+ padding: 2px 8px;
+}
+
+.form-row-event-select {
+ width: 244px;
+ height: 28px;
+ margin-bottom: 15px;
+ margin-left: 8px;
+}
+
+.runtime-description {
+ font-size: 12px;
+ margin-top: 5px;
+}
diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts
index 7ae941d72e1..9e3eed9980b 100644
--- a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts
+++ b/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts
@@ -36,9 +36,22 @@ import { VueWebview } from '../../../webviews/main'
import { Commands } from '../../../shared/vscode/commands2'
import { telemetry } from '../../../shared/telemetry/telemetry'
import { fs } from '../../../shared'
+import { ToolkitError } from '../../../shared'
+import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode'
const localize = nls.loadMessageBundle()
+export interface ResourceData {
+ logicalId: string
+ region: string
+ arn: string
+ location: string
+ handler: string
+ runtime: string
+ stackName: string
+ source: string
+}
+
export type AwsSamDebuggerConfigurationLoose = AwsSamDebuggerConfiguration & {
invokeTarget: Omit<
AwsSamDebuggerConfiguration['invokeTarget'],
@@ -55,7 +68,7 @@ interface SampleQuickPickItem extends vscode.QuickPickItem {
filename: string
}
-interface LaunchConfigPickItem extends vscode.QuickPickItem {
+export interface LaunchConfigPickItem extends vscode.QuickPickItem {
index: number
config?: AwsSamDebuggerConfiguration
}
@@ -66,7 +79,8 @@ export class SamInvokeWebview extends VueWebview {
public constructor(
private readonly extContext: ExtContext, // TODO(sijaden): get rid of `ExtContext`
- private readonly config?: AwsSamDebuggerConfiguration
+ private readonly config?: AwsSamDebuggerConfiguration,
+ private readonly data?: ResourceData
) {
super(SamInvokeWebview.sourcePath)
}
@@ -79,11 +93,11 @@ export class SamInvokeWebview extends VueWebview {
return this.config
}
- /**
- * Open a quick pick containing the names of launch configs in the `launch.json` array.
- * Filter out non-supported launch configs.
- */
- public async loadSamLaunchConfig(): Promise {
+ public getResourceData() {
+ return this.data
+ }
+
+ public async getSamLaunchConfigs(): Promise {
// TODO: Find a better way to infer this. Might need another arg from the frontend (depends on the context in which the launch config is made?)
const workspaceFolder = vscode.workspace.workspaceFolders?.length
? vscode.workspace.workspaceFolders[0]
@@ -94,7 +108,17 @@ export class SamInvokeWebview extends VueWebview {
}
const uri = workspaceFolder.uri
const launchConfig = new LaunchConfiguration(uri)
- const pickerItems = await getLaunchConfigQuickPickItems(launchConfig, uri)
+ const pickerItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri)
+ return pickerItems
+ }
+
+ /**
+ * Open a quick pick containing the names of launch configs in the `launch.json` array.
+ * Filter out non-supported launch configs.
+ */
+ public async loadSamLaunchConfig(): Promise {
+ const pickerItems: LaunchConfigPickItem[] = (await this.getSamLaunchConfigs()) || []
+
if (pickerItems.length === 0) {
pickerItems.push({
index: -1,
@@ -151,9 +175,14 @@ export class SamInvokeWebview extends VueWebview {
return sample
} catch (err) {
getLogger().error('Error getting manifest data..: %O', err as Error)
+ throw ToolkitError.chain(err, 'getting manifest data')
}
}
+ protected getTemplateRegistry() {
+ return globals.templateRegistry
+ }
+
/**
* Get all templates in the registry.
* Call back into the webview with the registry contents.
@@ -161,7 +190,7 @@ export class SamInvokeWebview extends VueWebview {
public async getTemplate() {
const items: (vscode.QuickPickItem & { templatePath: string })[] = []
const noTemplate = 'NOTEMPLATEFOUND'
- for (const template of (await globals.templateRegistry).items) {
+ for (const template of (await this.getTemplateRegistry()).items) {
const resources = template.item.Resources
if (resources) {
for (const resource of Object.keys(resources)) {
@@ -213,6 +242,41 @@ export class SamInvokeWebview extends VueWebview {
}
}
+ // This method serves as a wrapper around the backend function `openLaunchJsonFile`.
+ // The frontend cannot directly import and invoke backend functions like `openLaunchJsonFile`
+ // because doing so would break the webview environment by introducing server-side logic
+ // into client-side code. Instead, this method acts as an interface or bridge, allowing
+ // the frontend to request the backend to open the launch configuration file without
+ // directly coupling the frontend to backend-specific implementations.
+ public async openLaunchConfig() {
+ await openLaunchJsonFile()
+ }
+
+ public async promptFile() {
+ const fileLocations = await vscode.window.showOpenDialog({
+ openLabel: 'Open',
+ })
+
+ if (!fileLocations || fileLocations.length === 0) {
+ return undefined
+ }
+
+ try {
+ const fileContent = await fs.readFileBytes(fileLocations[0].fsPath)
+ return {
+ sample: fileContent,
+ selectedFilePath: fileLocations[0].fsPath,
+ selectedFile: this.getFileName(fileLocations[0].fsPath),
+ }
+ } catch (e) {
+ getLogger().error('readFileSync: Failed to read file at path %O', fileLocations[0].fsPath, e)
+ throw ToolkitError.chain(e, 'Failed to read selected file')
+ }
+ }
+
+ public getFileName(filePath: string): string {
+ return path.basename(filePath)
+ }
/**
* Open a quick pick containing the names of launch configs in the `launch.json` array, plus a "Create New Entry" entry.
* On selecting a name, overwrite the existing entry in the `launch.json` array and resave the file.
@@ -220,7 +284,7 @@ export class SamInvokeWebview extends VueWebview {
* @param config Config to save
*/
public async saveLaunchConfig(config: AwsSamDebuggerConfiguration): Promise {
- const uri = await getUriFromLaunchConfig(config)
+ const uri = await this.getUriFromLaunchConfig(config)
if (!uri) {
// TODO Localize
void vscode.window.showErrorMessage(
@@ -228,13 +292,14 @@ export class SamInvokeWebview extends VueWebview {
)
return
}
+
const launchConfig = new LaunchConfiguration(uri)
- const launchConfigItems = await getLaunchConfigQuickPickItems(launchConfig, uri)
+ const launchConfigItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri)
const pickerItems = [
{
label: addCodiconToString(
'add',
- localize('AWS.command.addSamDebugConfiguration', 'Add Debug Configuration')
+ localize('AWS.command.addSamDebugConfiguration', 'Add Local Invoke and Debug Configuration')
),
index: -1,
alwaysShow: true,
@@ -267,7 +332,7 @@ export class SamInvokeWebview extends VueWebview {
const response = await input.promptUser({ inputBox: ib })
if (response) {
await launchConfig.addDebugConfiguration(finalizeConfig(config, response))
- await openLaunchJsonFile()
+ await this.openLaunchConfig()
}
} else {
// use existing label
@@ -275,7 +340,7 @@ export class SamInvokeWebview extends VueWebview {
finalizeConfig(config, pickerResponse.label),
pickerResponse.index
)
- await openLaunchJsonFile()
+ await this.openLaunchConfig()
}
}
@@ -284,12 +349,12 @@ export class SamInvokeWebview extends VueWebview {
* TODO: Post validation failures back to webview?
* @param config Config to invoke
*/
- public async invokeLaunchConfig(config: AwsSamDebuggerConfiguration): Promise {
+ public async invokeLaunchConfig(config: AwsSamDebuggerConfiguration, source?: string): Promise {
const finalConfig = finalizeConfig(
resolveWorkspaceFolderVariable(undefined, config),
'Editor-Created Debug Config'
)
- const targetUri = await getUriFromLaunchConfig(finalConfig)
+ const targetUri = await this.getUriFromLaunchConfig(finalConfig)
const folder = targetUri ? vscode.workspace.getWorkspaceFolder(targetUri) : undefined
// Cloud9 currently can't resolve the `aws-sam` debug config provider.
@@ -298,12 +363,65 @@ export class SamInvokeWebview extends VueWebview {
// (Cloud9 also doesn't currently have variable resolution support anyways)
if (isCloud9()) {
const provider = new SamDebugConfigProvider(this.extContext)
- await provider.resolveDebugConfiguration(folder, finalConfig)
+ await provider.resolveDebugConfiguration(folder, finalConfig, undefined, source)
} else {
// startDebugging on VS Code goes through the whole resolution chain
await vscode.debug.startDebugging(folder, finalConfig)
}
}
+ public async getLaunchConfigQuickPickItems(
+ launchConfig: LaunchConfiguration,
+ uri: vscode.Uri
+ ): Promise