Skip to content

Commit 4671ced

Browse files
committed
Merge
2 parents d94657f + 853b3f8 commit 4671ced

File tree

21 files changed

+594
-50
lines changed

21 files changed

+594
-50
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "CodeWhisperer security scans now support Go files."
4+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,8 @@
754754
},
755755
{
756756
"id": "aws.codecatalyst",
757-
"name": "%AWS.codecatalyst.explorerTitle%"
757+
"name": "%AWS.codecatalyst.explorerTitle%",
758+
"when": "!isCloud9 || isCloud9CodeCatalyst"
758759
}
759760
],
760761
"aws-codewhisperer-reference-log": [

src/amazonq/auth/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ export class AuthController {
3030
}
3131

3232
private handleReAuth() {
33-
reconnect.execute(placeholder, amazonQChatSource)
33+
reconnect.execute(placeholder, amazonQChatSource, true)
3434
}
3535
}

src/amazonq/explorer/amazonQChildrenNodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const switchToAmazonQNode = () =>
5050
*/
5151
export const enableAmazonQNode = () =>
5252
// Simply trigger re-auth to obtain the proper scopes- same functionality as if requested in the chat window.
53-
reconnect.build(placeholder, cwTreeNodeSource).asTreeNode({
53+
reconnect.build(placeholder, cwTreeNodeSource, true).asTreeNode({
5454
label: localize('AWS.amazonq.enable', 'Enable'),
5555
iconPath: getIcon('vscode-debug-start'),
5656
contextValue: 'awsEnableAmazonQ',

src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry'
99
import { omit } from 'lodash'
1010
import { AuthUtil } from '../../codewhisperer/util/authUtil'
1111
import { ServiceOptions } from '../../shared/awsClientBuilder'
12-
import { ToolkitError } from '../../shared/errors'
1312
import globals from '../../shared/extensionGlobals'
1413
import { getLogger } from '../../shared/logger'
1514
import * as FeatureDevProxyClient from './featuredevproxyclient'
1615
import apiConfig = require('./codewhispererruntime-2022-11-11.json')
1716
import { featureName } from '../constants'
18-
import { ContentLengthError } from '../errors'
17+
import { ApiError, ContentLengthError, UnknownApiError } from '../errors'
1918
import { endpoint, region } from '../../codewhisperer/models/constants'
19+
import { isAwsError, isCodeWhispererStreamingServiceException } from '../../shared/errors'
2020

2121
// Create a client for featureDev proxy client based off of aws sdk v2
2222
export async function createFeatureDevProxyClient(): Promise<FeatureDevProxyClient> {
@@ -53,6 +53,15 @@ async function createFeatureDevStreamingClient(): Promise<CodeWhispererStreaming
5353
return streamingClient
5454
}
5555

56+
const streamResponseErrors: Record<string, number> = {
57+
ValidationException: 400,
58+
AccessDeniedException: 403,
59+
ResourceNotFoundException: 404,
60+
ConflictException: 409,
61+
ThrottlingException: 429,
62+
InternalServerException: 500,
63+
}
64+
5665
export class FeatureDevClient {
5766
private async getClient() {
5867
// Should not be stored for the whole session.
@@ -77,12 +86,14 @@ export class FeatureDevClient {
7786
})
7887
return conversationId
7988
} catch (e) {
80-
getLogger().error(
81-
`${featureName}: failed to start conversation: ${(e as Error).message} RequestId: ${
82-
(e as any).requestId
83-
}`
84-
)
85-
throw new ToolkitError((e as Error).message, { code: 'CreateConversationFailed' })
89+
if (isAwsError(e)) {
90+
getLogger().error(
91+
`${featureName}: failed to start conversation: ${e.message} RequestId: ${e.requestId}`
92+
)
93+
throw new ApiError(e.message, 'CreateConversation', e.code, e.statusCode ?? 400)
94+
}
95+
96+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateConversation')
8697
}
8798
}
8899

@@ -108,16 +119,18 @@ export class FeatureDevClient {
108119
requestId: response.$response.requestId,
109120
})
110121
return response
111-
} catch (e: any) {
112-
getLogger().error(
113-
`${featureName}: failed to generate presigned url: ${(e as Error).message} RequestId: ${
114-
(e as any).requestId
115-
}`
116-
)
117-
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
118-
throw new ContentLengthError()
122+
} catch (e) {
123+
if (isAwsError(e)) {
124+
getLogger().error(
125+
`${featureName}: failed to generate presigned url: ${e.message} RequestId: ${e.requestId}`
126+
)
127+
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
128+
throw new ContentLengthError()
129+
}
130+
throw new ApiError(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 400)
119131
}
120-
throw new ToolkitError((e as Error).message, { code: 'CreateUploadUrlFailed' })
132+
133+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateUploadUrl')
121134
}
122135
}
123136

@@ -153,10 +166,21 @@ export class FeatureDevClient {
153166
}
154167
return assistantResponse.join(' ')
155168
} catch (e) {
156-
getLogger().error(
157-
`${featureName}: failed to execute planning: ${(e as Error).message} RequestId: ${(e as any).requestId}`
158-
)
159-
throw new ToolkitError((e as Error).message, { code: 'GeneratePlanFailed' })
169+
if (isCodeWhispererStreamingServiceException(e)) {
170+
getLogger().error(
171+
`${featureName}: failed to execute planning: ${e.message} RequestId: ${
172+
e.$metadata.requestId ?? 'unknown'
173+
}`
174+
)
175+
throw new ApiError(
176+
e.message,
177+
'GeneratePlan',
178+
e.name,
179+
e.$metadata?.httpStatusCode ?? streamResponseErrors[e.name] ?? 500
180+
)
181+
}
182+
183+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'GeneratePlan')
160184
}
161185
}
162186
}

src/amazonqFeatureDev/errors.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ export class PrepareRepoFailedError extends ToolkitError {
5555
}
5656
}
5757

58+
export class IllegalStateTransition extends ToolkitError {
59+
constructor() {
60+
super('Illegal transition between states, restart the conversation', { code: 'IllegalStateTransition' })
61+
}
62+
}
63+
5864
export class ContentLengthError extends ToolkitError {
5965
constructor() {
6066
super(
@@ -64,6 +70,18 @@ export class ContentLengthError extends ToolkitError {
6470
}
6571
}
6672

73+
export class UnknownApiError extends ToolkitError {
74+
constructor(message: string, api: string) {
75+
super(message, { code: `${api}-Unknown` })
76+
}
77+
}
78+
79+
export class ApiError extends ToolkitError {
80+
constructor(message: string, api: string, errorName: string, errorCode: number) {
81+
super(message, { code: `${api}-${errorName}-${errorCode}` })
82+
}
83+
}
84+
6785
const denyListedErrors: string[] = ['Deserialization error', 'Inaccessible host']
6886

6987
export function createUserFacingErrorMessage(message: string) {

src/amazonqFeatureDev/session/sessionState.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as vscode from 'vscode'
88
import { ToolkitError } from '../../shared/errors'
99
import { getLogger } from '../../shared/logger'
1010
import { telemetry } from '../../shared/telemetry/telemetry'
11-
import { UserMessageNotFoundError } from '../errors'
11+
import { IllegalStateTransition, UserMessageNotFoundError } from '../errors'
1212
import { SessionState, SessionStateAction, SessionStateConfig, SessionStateInteraction } from '../types'
1313
import { prepareRepoData } from '../util/files'
1414
import { uploadCode } from '../util/upload'
@@ -23,9 +23,7 @@ export class ConversationNotStartedState implements Omit<SessionState, 'uploadId
2323
}
2424

2525
async interact(_action: SessionStateAction): Promise<SessionStateInteraction> {
26-
throw new ToolkitError('Illegal transition between states, restart the conversation', {
27-
code: 'IllegalStateTransition',
28-
})
26+
throw new IllegalStateTransition()
2927
}
3028
}
3129

src/codewhisperer/commands/basicCommands.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,13 @@ export const selectCustomizationPrompt = Commands.declare(
151151

152152
export const reconnect = Commands.declare(
153153
{ id: 'aws.codewhisperer.reconnect', compositeKey: { 1: 'source' } },
154-
() => async (_: VsCodeCommandArg, source: CodeWhispererSource) => {
155-
await AuthUtil.instance.reauthenticate()
156-
}
154+
() =>
155+
async (_: VsCodeCommandArg, source: CodeWhispererSource, addMissingScopes: boolean = false) => {
156+
if (typeof addMissingScopes !== 'boolean') {
157+
addMissingScopes = false
158+
}
159+
await AuthUtil.instance.reauthenticate(addMissingScopes)
160+
}
157161
)
158162

159163
/** Opens the Add Connections webview with CW highlighted */

src/codewhisperer/models/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ export const codeScanJavaPayloadSizeLimitBytes = Math.pow(2, 20) // 1 MB
200200

201201
export const codeScanCsharpPayloadSizeLimitBytes = Math.pow(2, 20) // 1 MB
202202

203-
export const codeScanRubyPayloadSizeLimitBytes = Math.pow(2, 20) // 1 MB
203+
export const codeScanRubyPayloadSizeLimitBytes = 200 * Math.pow(2, 10) // 200 KB
204+
205+
export const codeScanGoPayloadSizeLimitBytes = 200 * Math.pow(2, 10) // 200 KB
204206

205207
export const codeScanPythonPayloadSizeLimitBytes = 200 * Math.pow(2, 10) // 200 KB
206208

src/codewhisperer/util/authUtil.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ export const codeWhispererCoreScopes = [...scopesSsoAccountAccess, ...scopesCode
3636
export const codeWhispererChatScopes = [...codeWhispererCoreScopes, ...scopesCodeWhispererChat]
3737
export const amazonQScopes = [...codeWhispererChatScopes, ...scopesGumby, ...scopesFeatureDev]
3838

39-
export const awsBuilderIdSsoProfile = createBuilderIdProfile(codeWhispererChatScopes)
40-
4139
/**
4240
* "Core" are the CW scopes that existed before the addition of new scopes
4341
* for Amazon Q.
@@ -211,7 +209,7 @@ export class AuthUtil {
211209
let conn = (await this.auth.listConnections()).find(isBuilderIdConnection)
212210

213211
if (!conn) {
214-
conn = await this.auth.createConnection(awsBuilderIdSsoProfile)
212+
conn = await this.auth.createConnection(createBuilderIdProfile(codeWhispererChatScopes))
215213
} else if (!isValidCodeWhispererChatConnection(conn)) {
216214
conn = await this.secondaryAuth.addScopes(conn, codeWhispererChatScopes)
217215
}
@@ -304,23 +302,27 @@ export class AuthUtil {
304302
return connectionExpired
305303
}
306304

307-
public async reauthenticate() {
305+
public async reauthenticate(addMissingScopes: boolean = false) {
308306
try {
309307
if (this.conn?.type !== 'sso') {
310308
return
311309
}
312310

313311
// Edge Case: With the addition of Amazon Q/Chat scopes we may need to add
314-
// the new scopes to existing connections.
315-
if (isBuilderIdConnection(this.conn) && !isValidCodeWhispererChatConnection(this.conn)) {
316-
const conn = await this.secondaryAuth.addScopes(this.conn, codeWhispererChatScopes)
317-
this.secondaryAuth.useNewConnection(conn)
318-
} else if (isIdcSsoConnection(this.conn) && !isValidAmazonQConnection(this.conn)) {
319-
const conn = await this.secondaryAuth.addScopes(this.conn, amazonQScopes)
320-
this.secondaryAuth.useNewConnection(conn)
321-
} else {
322-
await this.auth.reauthenticate(this.conn)
312+
// the new scopes to existing pre-chat connections.
313+
if (addMissingScopes) {
314+
if (isBuilderIdConnection(this.conn) && !isValidCodeWhispererChatConnection(this.conn)) {
315+
const conn = await this.secondaryAuth.addScopes(this.conn, codeWhispererChatScopes)
316+
await this.secondaryAuth.useNewConnection(conn)
317+
return
318+
} else if (isIdcSsoConnection(this.conn) && !isValidAmazonQConnection(this.conn)) {
319+
const conn = await this.secondaryAuth.addScopes(this.conn, amazonQScopes)
320+
await this.secondaryAuth.useNewConnection(conn)
321+
return
322+
}
323323
}
324+
325+
await this.auth.reauthenticate(this.conn)
324326
} catch (err) {
325327
throw ToolkitError.chain(err, 'Unable to authenticate connection')
326328
} finally {

0 commit comments

Comments
 (0)